diff options
Diffstat (limited to 'chromium/base')
328 files changed, 15859 insertions, 6998 deletions
diff --git a/chromium/base/OWNERS b/chromium/base/OWNERS index f85ea53c41f..feb39cc1d8d 100644 --- a/chromium/base/OWNERS +++ b/chromium/base/OWNERS @@ -2,7 +2,6 @@ mark@chromium.org darin@chromium.org brettw@chromium.org willchan@chromium.org -jar@chromium.org ajwong@chromium.org thakis@chromium.org diff --git a/chromium/base/allocator/allocator.gyp b/chromium/base/allocator/allocator.gyp index ef98d09482b..4841f58c9a4 100644 --- a/chromium/base/allocator/allocator.gyp +++ b/chromium/base/allocator/allocator.gyp @@ -253,6 +253,8 @@ '<(tcmalloc_dir)/src/gperftools/profiler.h', '<(tcmalloc_dir)/src/gperftools/stacktrace.h', '<(tcmalloc_dir)/src/gperftools/tcmalloc.h', + '<(tcmalloc_dir)/src/heap-checker-bcad.cc', + '<(tcmalloc_dir)/src/heap-checker.cc', '<(tcmalloc_dir)/src/libc_override.h', '<(tcmalloc_dir)/src/libc_override_gcc_and_weak.h', '<(tcmalloc_dir)/src/libc_override_glibc.h', @@ -325,6 +327,10 @@ ], }, }, + # Disable the heap checker in tcmalloc. + 'defines': [ + 'NO_HEAP_CHECK', + ], 'conditions': [ ['OS=="linux" and clang_type_profiler==1', { 'dependencies': [ @@ -371,18 +377,9 @@ # included by allocator_shim.cc 'debugallocation_shim.cc', - # heap-profiler/checker/cpuprofiler + # cpuprofiler '<(tcmalloc_dir)/src/base/thread_lister.c', '<(tcmalloc_dir)/src/base/thread_lister.h', - '<(tcmalloc_dir)/src/deep-heap-profile.cc', - '<(tcmalloc_dir)/src/deep-heap-profile.h', - '<(tcmalloc_dir)/src/heap-checker-bcad.cc', - '<(tcmalloc_dir)/src/heap-checker.cc', - '<(tcmalloc_dir)/src/heap-profiler.cc', - '<(tcmalloc_dir)/src/heap-profile-table.cc', - '<(tcmalloc_dir)/src/heap-profile-table.h', - '<(tcmalloc_dir)/src/memory_region_map.cc', - '<(tcmalloc_dir)/src/memory_region_map.h', '<(tcmalloc_dir)/src/profiledata.cc', '<(tcmalloc_dir)/src/profiledata.h', '<(tcmalloc_dir)/src/profile-handler.cc', @@ -439,30 +436,6 @@ '-fvtable-verify=preinit', ], }], - [ 'linux_keep_shadow_stacks==1', { - 'sources': [ - '<(tcmalloc_dir)/src/linux_shadow_stacks.cc', - '<(tcmalloc_dir)/src/linux_shadow_stacks.h', - '<(tcmalloc_dir)/src/stacktrace_shadow-inl.h', - ], - 'cflags': [ - '-finstrument-functions', - ], - 'defines': [ - 'KEEP_SHADOW_STACKS', - ], - }], - [ 'linux_use_heapchecker==0', { - # Do not compile and link the heapchecker source. - 'sources!': [ - '<(tcmalloc_dir)/src/heap-checker-bcad.cc', - '<(tcmalloc_dir)/src/heap-checker.cc', - ], - # Disable the heap checker in tcmalloc. - 'defines': [ - 'NO_HEAP_CHECK', - ], - }], ['order_profiling != 0', { 'target_conditions' : [ ['_toolset=="target"', { diff --git a/chromium/base/allocator/allocator_shim.cc b/chromium/base/allocator/allocator_shim.cc index 1d8229117d7..9b3b50c4ee4 100644 --- a/chromium/base/allocator/allocator_shim.cc +++ b/chromium/base/allocator/allocator_shim.cc @@ -10,11 +10,9 @@ #include "base/sysinfo.h" #include "jemalloc.h" -// When defined, different heap allocators can be used via an environment -// variable set before running the program. This may reduce the amount -// of inlining that we get with malloc/free/etc. Disabling makes it -// so that only tcmalloc can be used. -#define ENABLE_DYNAMIC_ALLOCATOR_SWITCHING +// This shim make it possible to use different allocators via an environment +// variable set before running the program. This may reduce the +// amount of inlining that we get with malloc/free/etc. // TODO(mbelshe): Ensure that all calls to tcmalloc have the proper call depth // from the "user code" so that debugging tools (HeapChecker) can work. @@ -119,7 +117,6 @@ extern "C" { void* malloc(size_t size) __THROW { void* ptr; for (;;) { -#ifdef ENABLE_DYNAMIC_ALLOCATOR_SWITCHING switch (allocator) { case JEMALLOC: ptr = je_malloc(size); @@ -133,10 +130,6 @@ void* malloc(size_t size) __THROW { ptr = do_malloc(size); break; } -#else - // TCMalloc case. - ptr = do_malloc(size); -#endif if (ptr) return ptr; @@ -147,7 +140,6 @@ void* malloc(size_t size) __THROW { } void free(void* p) __THROW { -#ifdef ENABLE_DYNAMIC_ALLOCATOR_SWITCHING switch (allocator) { case JEMALLOC: je_free(p); @@ -156,10 +148,10 @@ void free(void* p) __THROW { case WINLFH: win_heap_free(p); return; + case TCMALLOC: + do_free(p); + return; } -#endif - // TCMalloc case. - do_free(p); } void* realloc(void* ptr, size_t size) __THROW { @@ -171,7 +163,6 @@ void* realloc(void* ptr, size_t size) __THROW { void* new_ptr; for (;;) { -#ifdef ENABLE_DYNAMIC_ALLOCATOR_SWITCHING switch (allocator) { case JEMALLOC: new_ptr = je_realloc(ptr, size); @@ -185,10 +176,6 @@ void* realloc(void* ptr, size_t size) __THROW { new_ptr = do_realloc(ptr, size); break; } -#else - // TCMalloc case. - new_ptr = do_realloc(ptr, size); -#endif // Subtle warning: NULL return does not alwas indicate out-of-memory. If // the requested new size is zero, realloc should free the ptr and return @@ -203,7 +190,6 @@ void* realloc(void* ptr, size_t size) __THROW { // TODO(mbelshe): Implement this for other allocators. void malloc_stats(void) __THROW { -#ifdef ENABLE_DYNAMIC_ALLOCATOR_SWITCHING switch (allocator) { case JEMALLOC: // No stats. @@ -212,15 +198,15 @@ void malloc_stats(void) __THROW { case WINLFH: // No stats. return; + case TCMALLOC: + tc_malloc_stats(); + return; } -#endif - tc_malloc_stats(); } #ifdef WIN32 extern "C" size_t _msize(void* p) { -#ifdef ENABLE_DYNAMIC_ALLOCATOR_SWITCHING switch (allocator) { case JEMALLOC: return je_msize(p); @@ -228,7 +214,8 @@ extern "C" size_t _msize(void* p) { case WINLFH: return win_heap_msize(p); } -#endif + + // TCMALLOC return MallocExtension::instance()->GetAllocatedSize(p); } @@ -238,7 +225,6 @@ extern "C" intptr_t _get_heap_handle() { } static bool get_allocator_waste_size_thunk(size_t* size) { -#ifdef ENABLE_DYNAMIC_ALLOCATOR_SWITCHING switch (allocator) { case JEMALLOC: case WINHEAP: @@ -246,7 +232,6 @@ static bool get_allocator_waste_size_thunk(size_t* size) { // TODO(alexeif): Implement for allocators other than tcmalloc. return false; } -#endif size_t heap_size, allocated_bytes, unmapped_bytes; MallocExtension* ext = MallocExtension::instance(); if (ext->GetNumericProperty("generic.heap_size", &heap_size) && @@ -270,7 +255,6 @@ static void release_free_memory_thunk() { // The CRT heap initialization stub. extern "C" int _heap_init() { -#ifdef ENABLE_DYNAMIC_ALLOCATOR_SWITCHING // Don't use the environment variable if ADDRESS_SANITIZER is defined on // Windows, as the implementation requires Winheap to be the allocator. #if !(defined(ADDRESS_SANITIZER) && defined(OS_WIN)) @@ -299,7 +283,7 @@ extern "C" int _heap_init() { // fall through break; } -#endif + // Initializing tcmalloc. // We intentionally leak this object. It lasts for the process // lifetime. Trying to teardown at _heap_term() is so late that @@ -346,7 +330,6 @@ void* _aligned_malloc(size_t size, size_t alignment) { void* ptr; for (;;) { -#ifdef ENABLE_DYNAMIC_ALLOCATOR_SWITCHING switch (allocator) { case JEMALLOC: ptr = je_memalign(alignment, size); @@ -360,10 +343,7 @@ void* _aligned_malloc(size_t size, size_t alignment) { ptr = tc_memalign(alignment, size); break; } -#else - // TCMalloc case. - ptr = tc_memalign(alignment, size); -#endif + if (ptr) { // Sanity check alignment. DCHECK_EQ(reinterpret_cast<uintptr_t>(ptr) & (alignment - 1), 0U); @@ -380,7 +360,6 @@ void _aligned_free(void* p) { // Both JEMalloc and TCMalloc return pointers from memalign() that are safe to // use with free(). Pointers allocated with win_heap_memalign() MUST be freed // via win_heap_memalign_free() since the aligned pointer is not the real one. -#ifdef ENABLE_DYNAMIC_ALLOCATOR_SWITCHING switch (allocator) { case JEMALLOC: je_free(p); @@ -389,10 +368,9 @@ void _aligned_free(void* p) { case WINLFH: win_heap_memalign_free(p); return; + case TCMALLOC: + do_free(p); } -#endif - // TCMalloc case. - do_free(p); } #endif // WIN32 @@ -405,7 +383,6 @@ namespace base { namespace allocator { void SetupSubprocessAllocator() { -#ifdef ENABLE_DYNAMIC_ALLOCATOR_SWITCHING size_t primary_length = 0; getenv_s(&primary_length, NULL, 0, primary_name); @@ -427,7 +404,6 @@ void SetupSubprocessAllocator() { int ret_val = _putenv_s(primary_name, secondary_value); DCHECK_EQ(0, ret_val); } -#endif // ENABLE_DYNAMIC_ALLOCATOR_SWITCHING } void* TCMallocDoMallocForTest(size_t size) { diff --git a/chromium/base/allocator/prep_libc.py b/chromium/base/allocator/prep_libc.py index e13e9e3b7e0..ba25cea092f 100755 --- a/chromium/base/allocator/prep_libc.py +++ b/chromium/base/allocator/prep_libc.py @@ -4,7 +4,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. # -# This script takes libcmt.lib for VS2005/08/10 and removes the allocation +# This script takes libcmt.lib for VS2005/08/10/12/13 and removes the allocation # related functions from it. # # Usage: prep_libc.py <VCLibDir> <OutputDir> <arch> @@ -51,7 +51,10 @@ def main(): 'F:\\dd\\vctools\\crt_bld\\' + bindir + \ '\\crt\\src\\build\\' + objdir + '\\mt_obj\\nativec\\\\', 'F:\\dd\\vctools\\crt_bld\\' + bindir + \ - '\\crt\\src\\build\\' + objdir + '\\mt_obj\\nativecpp\\\\' ] + '\\crt\\src\\build\\' + objdir + '\\mt_obj\\nativecpp\\\\', + 'f:\\binaries\\Intermediate\\vctools\\crt_bld\\' + bindir + \ + '\\crt\\prebuild\\build\\INTEL\\mt_obj\\cpp_obj\\\\', + ] objfiles = ['malloc', 'free', 'realloc', 'new', 'delete', 'new2', 'delete2', 'align', 'msize', 'heapinit', 'expand', 'heapchk', 'heapwalk', diff --git a/chromium/base/allocator/win_allocator.cc b/chromium/base/allocator/win_allocator.cc index 899b867d11e..ee451f54610 100644 --- a/chromium/base/allocator/win_allocator.cc +++ b/chromium/base/allocator/win_allocator.cc @@ -56,7 +56,12 @@ void* win_heap_memalign(size_t alignment, size_t size) { DCHECK_LT(size, allocation_size); DCHECK_LT(alignment, allocation_size); + // Since we're directly calling the allocator function, before OOM handling, + // we need to NULL check to ensure the allocation succeeded. void* ptr = win_heap_malloc(allocation_size); + if (!ptr) + return ptr; + char* aligned_ptr = static_cast<char*>(ptr) + sizeof(void*); aligned_ptr += alignment - reinterpret_cast<uintptr_t>(aligned_ptr) & (alignment - 1); diff --git a/chromium/base/android/OWNERS b/chromium/base/android/OWNERS index ebc6e2624f0..87d5d224978 100644 --- a/chromium/base/android/OWNERS +++ b/chromium/base/android/OWNERS @@ -1,3 +1,2 @@ bulach@chromium.org -joth@chromium.org yfriedman@chromium.org diff --git a/chromium/base/android/command_line_android.cc b/chromium/base/android/command_line_android.cc new file mode 100644 index 00000000000..895ffab4f95 --- /dev/null +++ b/chromium/base/android/command_line_android.cc @@ -0,0 +1,85 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/android/command_line_android.h" + +#include "base/android/jni_array.h" +#include "base/android/jni_string.h" +#include "base/command_line.h" +#include "base/logging.h" +#include "jni/CommandLine_jni.h" + +using base::android::ConvertUTF8ToJavaString; +using base::android::ConvertJavaStringToUTF8; + +namespace { + +void AppendJavaStringArrayToCommandLine(JNIEnv* env, + jobjectArray array, + bool includes_program) { + std::vector<std::string> vec; + if (array) + base::android::AppendJavaStringArrayToStringVector(env, array, &vec); + if (!includes_program) + vec.insert(vec.begin(), ""); + CommandLine extra_command_line(vec); + CommandLine::ForCurrentProcess()->AppendArguments(extra_command_line, + includes_program); +} + +} // namespace + +static void Reset(JNIEnv* env, jclass clazz) { + CommandLine::Reset(); +} + +static jboolean HasSwitch(JNIEnv* env, jclass clazz, jstring jswitch) { + std::string switch_string(ConvertJavaStringToUTF8(env, jswitch)); + return CommandLine::ForCurrentProcess()->HasSwitch(switch_string); +} + +static jstring GetSwitchValue(JNIEnv* env, jclass clazz, jstring jswitch) { + std::string switch_string(ConvertJavaStringToUTF8(env, jswitch)); + std::string value(CommandLine::ForCurrentProcess()->GetSwitchValueNative( + switch_string)); + if (value.empty()) + return 0; + // OK to release, JNI binding. + return ConvertUTF8ToJavaString(env, value).Release(); +} + +static void AppendSwitch(JNIEnv* env, jclass clazz, jstring jswitch) { + std::string switch_string(ConvertJavaStringToUTF8(env, jswitch)); + CommandLine::ForCurrentProcess()->AppendSwitch(switch_string); +} + +static void AppendSwitchWithValue(JNIEnv* env, jclass clazz, + jstring jswitch, jstring jvalue) { + std::string switch_string(ConvertJavaStringToUTF8(env, jswitch)); + std::string value_string (ConvertJavaStringToUTF8(env, jvalue)); + CommandLine::ForCurrentProcess()->AppendSwitchASCII(switch_string, + value_string); +} + +static void AppendSwitchesAndArguments(JNIEnv* env, jclass clazz, + jobjectArray array) { + AppendJavaStringArrayToCommandLine(env, array, false); +} + +namespace base { +namespace android { + +void InitNativeCommandLineFromJavaArray(JNIEnv* env, jobjectArray array) { + // TODO(port): Make an overload of Init() that takes StringVector rather than + // have to round-trip via AppendArguments. + CommandLine::Init(0, NULL); + AppendJavaStringArrayToCommandLine(env, array, true); +} + +bool RegisterCommandLine(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace android +} // namespace base diff --git a/chromium/base/android/command_line_android.h b/chromium/base/android/command_line_android.h new file mode 100644 index 00000000000..cf117e2b4e5 --- /dev/null +++ b/chromium/base/android/command_line_android.h @@ -0,0 +1,26 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_ANDROID_COMMAND_LINE_ANDROID_H_ +#define BASE_ANDROID_COMMAND_LINE_ANDROID_H_ + +#include <jni.h> + +#include "base/base_export.h" + +namespace base { +namespace android { + +// Appends all strings in the given array as flags to the Chrome command line. +void BASE_EXPORT InitNativeCommandLineFromJavaArray( + JNIEnv* env, + jobjectArray init_command_line); + +// JNI registration boilerplate. +bool RegisterCommandLine(JNIEnv* env); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_COMMAND_LINE_ANDROID_H_ diff --git a/chromium/base/android/content_uri_utils.cc b/chromium/base/android/content_uri_utils.cc new file mode 100644 index 00000000000..64d6ad24226 --- /dev/null +++ b/chromium/base/android/content_uri_utils.cc @@ -0,0 +1,39 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/android/content_uri_utils.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/platform_file.h" +#include "jni/ContentUriUtils_jni.h" + +using base::android::ConvertUTF8ToJavaString; + +namespace base { + +bool RegisterContentUriUtils(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +bool ContentUriExists(const FilePath& content_uri) { + JNIEnv* env = base::android::AttachCurrentThread(); + ScopedJavaLocalRef<jstring> j_uri = + ConvertUTF8ToJavaString(env, content_uri.value()); + return Java_ContentUriUtils_contentUriExists( + env, base::android::GetApplicationContext(), j_uri.obj()); +} + +int OpenContentUriForRead(const FilePath& content_uri) { + JNIEnv* env = base::android::AttachCurrentThread(); + ScopedJavaLocalRef<jstring> j_uri = + ConvertUTF8ToJavaString(env, content_uri.value()); + jint fd = Java_ContentUriUtils_openContentUriForRead( + env, base::android::GetApplicationContext(), j_uri.obj()); + if (fd < 0) + return base::kInvalidPlatformFileValue; + return fd; +} + +} // namespace base diff --git a/chromium/base/android/content_uri_utils.h b/chromium/base/android/content_uri_utils.h new file mode 100644 index 00000000000..ec820efbd26 --- /dev/null +++ b/chromium/base/android/content_uri_utils.h @@ -0,0 +1,27 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_ANDROID_CONTENT_URI_UTILS_H_ +#define BASE_ANDROID_CONTENT_URI_UTILS_H_ + +#include <jni.h> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/files/file_path.h" + +namespace base { + +bool RegisterContentUriUtils(JNIEnv* env); + +// Opens a content uri for read and returns the file descriptor to the caller. +// Returns -1 if the uri is invalid. +BASE_EXPORT int OpenContentUriForRead(const FilePath& content_uri); + +// Check whether a content uri exists. +BASE_EXPORT bool ContentUriExists(const FilePath& content_uri); + +} // namespace base + +#endif // BASE_ANDROID_CONTENT_URI_UTILS_H_ diff --git a/chromium/base/android/sys_utils.cc b/chromium/base/android/sys_utils.cc index bc18f81c721..efd13df3d42 100644 --- a/chromium/base/android/sys_utils.cc +++ b/chromium/base/android/sys_utils.cc @@ -8,16 +8,6 @@ #include "base/sys_info.h" #include "jni/SysUtils_jni.h" -const int64 kLowEndMemoryThreshold = 1024 * 1024 * 512; // 512 mb. - -// Only support low end device changes on builds greater than JB MR2. -const int kLowEndSdkIntThreshold = 18; - -// Defined and called by JNI -static jboolean IsLowEndDevice(JNIEnv* env, jclass clazz) { - return base::android::SysUtils::IsLowEndDevice(); -} - namespace base { namespace android { @@ -25,9 +15,24 @@ bool SysUtils::Register(JNIEnv* env) { return RegisterNativesImpl(env); } +bool SysUtils::IsLowEndDeviceFromJni() { + JNIEnv* env = AttachCurrentThread(); + return Java_SysUtils_isLowEndDevice(env); +} + bool SysUtils::IsLowEndDevice() { - return SysInfo::AmountOfPhysicalMemory() <= kLowEndMemoryThreshold && - BuildInfo::GetInstance()->sdk_int() > kLowEndSdkIntThreshold; + static bool is_low_end = IsLowEndDeviceFromJni(); + return is_low_end; +} + +size_t SysUtils::AmountOfPhysicalMemoryKBFromJni() { + JNIEnv* env = AttachCurrentThread(); + return static_cast<size_t>(Java_SysUtils_amountOfPhysicalMemoryKB(env)); +} + +size_t SysUtils::AmountOfPhysicalMemoryKB() { + static size_t amount_of_ram = AmountOfPhysicalMemoryKBFromJni(); + return amount_of_ram; } SysUtils::SysUtils() { } diff --git a/chromium/base/android/sys_utils.h b/chromium/base/android/sys_utils.h index 78122ff572d..9b3152b466c 100644 --- a/chromium/base/android/sys_utils.h +++ b/chromium/base/android/sys_utils.h @@ -14,10 +14,17 @@ class BASE_EXPORT SysUtils { public: static bool Register(JNIEnv* env); + // Returns true iff this is a low-end device. static bool IsLowEndDevice(); + // Return the device's RAM size in kilo-bytes. Used for testing. + static size_t AmountOfPhysicalMemoryKB(); + private: SysUtils(); + + static bool IsLowEndDeviceFromJni(); + static size_t AmountOfPhysicalMemoryKBFromJni(); }; } // namespace android diff --git a/chromium/base/android/sys_utils_unittest.cc b/chromium/base/android/sys_utils_unittest.cc new file mode 100644 index 00000000000..2a5a17d8532 --- /dev/null +++ b/chromium/base/android/sys_utils_unittest.cc @@ -0,0 +1,23 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/android/sys_utils.h" + +#include <unistd.h> + +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace android { + +TEST(SysUtils, AmountOfPhysicalMemory) { + // Check that the RAM size reported by sysconf() matches the one + // computed by base::android::SysUtils::AmountOfPhysicalMemory(). + size_t sys_ram_size = + static_cast<size_t>(sysconf(_SC_PHYS_PAGES) * PAGE_SIZE); + EXPECT_EQ(sys_ram_size, SysUtils::AmountOfPhysicalMemoryKB() * 1024UL); +} + +} // namespace android +} // namespace base diff --git a/chromium/base/base.gyp b/chromium/base/base.gyp index 39f6015dba1..407d49d0582 100644 --- a/chromium/base/base.gyp +++ b/chromium/base/base.gyp @@ -35,7 +35,7 @@ ], }, 'conditions': [ - ['use_glib==1', { + ['desktop_linux == 1 or chromeos == 1', { 'conditions': [ ['chromeos==1', { 'sources/': [ ['include', '_chromeos\\.cc$'] ] @@ -51,7 +51,6 @@ ], 'dependencies': [ 'symbolize', - '../build/linux/system.gyp:glib', 'xdg_mime', ], 'defines': [ @@ -60,15 +59,20 @@ 'cflags': [ '-Wno-write-strings', ], - 'export_dependent_settings': [ - '../build/linux/system.gyp:glib', - ], - }, { # use_glib!=1 + }, { # desktop_linux == 0 and chromeos == 0 'sources/': [ ['exclude', '/xdg_user_dirs/'], ['exclude', '_nss\\.cc$'], ], }], + ['use_glib==1', { + 'dependencies': [ + '../build/linux/system.gyp:glib', + ], + 'export_dependent_settings': [ + '../build/linux/system.gyp:glib', + ], + }], ['use_x11==1', { 'dependencies': [ '../build/linux/system.gyp:x11', @@ -77,6 +81,14 @@ '../build/linux/system.gyp:x11', ], }], + ['use_aura==1 and use_x11==1', { + 'dependencies': [ + '../build/linux/system.gyp:xrandr', + ], + 'export_dependent_settings': [ + '../build/linux/system.gyp:xrandr', + ], + }], ['OS == "android" and _toolset == "host"', { # Always build base as a static_library for host toolset, even if # we're doing a component build. Specifically, we only care about the @@ -335,6 +347,20 @@ 'i18n/string_search.h', 'i18n/time_formatting.cc', 'i18n/time_formatting.h', + 'i18n/timezone.cc', + 'i18n/timezone.h', + ], + }, + { + 'target_name': 'base_message_loop_tests', + 'type': 'static_library', + 'dependencies': [ + 'base', + '../testing/gtest.gyp:gtest', + ], + 'sources': [ + 'message_loop/message_loop_test.cc', + 'message_loop/message_loop_test.h', ], }, { @@ -376,14 +402,16 @@ 'prefs/pref_registry_simple.h', 'prefs/pref_service.cc', 'prefs/pref_service.h', - 'prefs/pref_service_builder.cc', - 'prefs/pref_service_builder.h', + 'prefs/pref_service_factory.cc', + 'prefs/pref_service_factory.h', 'prefs/pref_store.cc', 'prefs/pref_store.h', 'prefs/pref_value_map.cc', 'prefs/pref_value_map.h', 'prefs/pref_value_store.cc', 'prefs/pref_value_store.h', + 'prefs/scoped_user_pref_update.cc', + 'prefs/scoped_user_pref_update.h', 'prefs/value_map_pref_store.cc', 'prefs/value_map_pref_store.h', ], @@ -450,6 +478,7 @@ 'android/jni_string_unittest.cc', 'android/path_utils_unittest.cc', 'android/scoped_java_ref_unittest.cc', + 'android/sys_utils_unittest.cc', 'async_socket_io_handler_unittest.cc', 'at_exit_unittest.cc', 'atomicops_unittest.cc', @@ -460,7 +489,8 @@ 'bits_unittest.cc', 'build_time_unittest.cc', 'callback_helpers_unittest.cc', - 'callback_registry_unittest.cc', + 'callback_list_unittest.cc', + 'callback_list_unittest.nc', 'callback_unittest.cc', 'callback_unittest.nc', 'cancelable_callback_unittest.cc', @@ -486,6 +516,7 @@ 'file_version_info_unittest.cc', 'files/dir_reader_posix_unittest.cc', 'files/file_path_unittest.cc', + 'files/file_unittest.cc', 'files/file_util_proxy_unittest.cc', 'files/important_file_writer_unittest.cc', 'files/scoped_temp_dir_unittest.cc', @@ -501,6 +532,7 @@ 'i18n/rtl_unittest.cc', 'i18n/string_search_unittest.cc', 'i18n/time_formatting_unittest.cc', + 'i18n/timezone_unittest.cc', 'ini_parser_unittest.cc', 'ios/device_util_unittest.mm', 'json/json_parser_unittest.cc', @@ -520,7 +552,9 @@ 'mac/scoped_sending_event_unittest.mm', 'md5_unittest.cc', 'memory/aligned_memory_unittest.cc', + 'memory/discardable_memory_allocator_android_unittest.cc', 'memory/discardable_memory_unittest.cc', + 'memory/discardable_memory_provider_unittest.cc', 'memory/linked_ptr_unittest.cc', 'memory/ref_counted_memory_unittest.cc', 'memory/ref_counted_unittest.cc', @@ -542,6 +576,7 @@ 'metrics/bucket_ranges_unittest.cc', 'metrics/field_trial_unittest.cc', 'metrics/histogram_base_unittest.cc', + 'metrics/histogram_delta_serialization_unittest.cc', 'metrics/histogram_unittest.cc', 'metrics/sparse_histogram_unittest.cc', 'metrics/stats_table_unittest.cc', @@ -564,12 +599,13 @@ 'prefs/pref_service_unittest.cc', 'prefs/pref_value_map_unittest.cc', 'prefs/pref_value_store_unittest.cc', + 'prefs/scoped_user_pref_update_unittest.cc', 'process/memory_unittest.cc', 'process/memory_unittest_mac.h', 'process/memory_unittest_mac.mm', - 'process/process_metrics_unittests.cc', + 'process/process_metrics_unittest.cc', + 'process/process_metrics_unittest_ios.cc', 'process/process_util_unittest.cc', - 'process/process_util_unittest_ios.cc', 'profiler/tracked_time_unittest.cc', 'rand_util_unittest.cc', 'safe_numerics_unittest.cc', @@ -595,6 +631,7 @@ 'strings/sys_string_conversions_unittest.cc', 'strings/utf_offset_string_conversions_unittest.cc', 'strings/utf_string_conversions_unittest.cc', + 'sync_socket_unittest.cc', 'synchronization/cancellation_flag_unittest.cc', 'synchronization/condition_variable_unittest.cc', 'synchronization/lock_unittest.cc', @@ -606,6 +643,7 @@ 'template_util_unittest.cc', 'test/expectations/expectation_unittest.cc', 'test/expectations/parser_unittest.cc', + 'test/test_reg_util_win_unittest.cc', 'test/trace_event_analyzer_unittest.cc', 'threading/non_thread_safe_unittest.cc', 'threading/platform_thread_unittest.cc', @@ -644,7 +682,6 @@ 'win/registry_unittest.cc', 'win/scoped_bstr_unittest.cc', 'win/scoped_comptr_unittest.cc', - 'win/scoped_handle_unittest.cc', 'win/scoped_process_information_unittest.cc', 'win/scoped_variant_unittest.cc', 'win/shortcut_unittest.cc', @@ -655,6 +692,7 @@ 'dependencies': [ 'base', 'base_i18n', + 'base_message_loop_tests', 'base_prefs', 'base_prefs_test_support', 'base_static', @@ -672,7 +710,7 @@ 'module_dir': 'base' }, 'conditions': [ - ['use_glib==1', { + ['desktop_linux == 1 or chromeos == 1', { 'defines': [ 'USE_SYMBOLIZE', ], @@ -700,26 +738,6 @@ # iOS does not use message_pump_libevent. ['exclude', '^message_loop/message_pump_libevent_unittest\\.cc$'], ], - 'conditions': [ - ['coverage != 0', { - 'sources!': [ - # These sources can't be built with coverage due to a toolchain - # bug: http://openradar.appspot.com/radar?id=1499403 - 'json/json_reader_unittest.cc', - 'strings/string_piece_unittest.cc', - - # These tests crash when run with coverage turned on due to an - # issue with llvm_gcda_increment_indirect_counter: - # http://crbug.com/156058 - 'debug/trace_event_unittest.cc', - 'debug/trace_event_unittest.h', - 'logging_unittest.cc', - 'string_util_unittest.cc', - 'test/trace_event_analyzer_unittest.cc', - 'utf_offset_string_conversions_unittest.cc', - ], - }], - ], 'actions': [ { 'action_name': 'copy_test_data', @@ -733,7 +751,7 @@ }, ], }], - ['use_glib==1', { + ['desktop_linux == 1 or chromeos == 1', { 'sources!': [ 'file_version_info_unittest.cc', ], @@ -748,11 +766,19 @@ }], ], 'dependencies': [ - '../build/linux/system.gyp:glib', '../build/linux/system.gyp:ssl', + ], + }], + ['use_x11 == 1', { + 'dependencies': [ '../tools/xdisplaycheck/xdisplaycheck.gyp:xdisplaycheck', ], - }, { # use_glib!=1 + }], + ['use_glib == 1', { + 'dependencies': [ + '../build/linux/system.gyp:glib', + ], + }, { # use_glib == 0 'sources!': [ 'message_loop/message_pump_glib_unittest.cc', ] @@ -804,11 +830,14 @@ ['exclude', '^win/'], ], 'sources!': [ - 'debug/trace_event_win_unittest.cc', - 'time/time_win_unittest.cc', 'win/win_util_unittest.cc', ], }], + ['use_aura==1 and use_x11==1', { + 'sources': [ + 'x11/edid_parser_x11_unittest.cc', + ], + }], ['use_system_nspr==1', { 'dependencies': [ 'third_party/nspr/nspr.gyp:nspr', @@ -866,6 +895,15 @@ 'test/test_file_util_linux.cc', ], }], + ['OS == "android"', { + 'dependencies': [ + 'base_unittests_jni_headers', + 'base_java_unittest_support', + ], + 'include_dirs': [ + '<(SHARED_INTERMEDIATE_DIR)/base', + ], + }], ], 'sources': [ 'test/expectations/expectation.cc', @@ -874,6 +912,15 @@ 'test/expectations/parser.h', 'test/gtest_xml_util.cc', 'test/gtest_xml_util.h', + 'test/launcher/test_launcher.cc', + 'test/launcher/test_launcher.h', + 'test/launcher/test_result.cc', + 'test/launcher/test_result.h', + 'test/launcher/test_results_tracker.cc', + 'test/launcher/test_results_tracker.h', + 'test/launcher/unit_test_launcher.cc', + 'test/launcher/unit_test_launcher.h', + 'test/launcher/unit_test_launcher_ios.cc', 'test/mock_chrome_application_mac.h', 'test/mock_chrome_application_mac.mm', 'test/mock_devices_changed_observer.cc', @@ -885,16 +932,12 @@ 'test/multiprocess_test_android.cc', 'test/null_task_runner.cc', 'test/null_task_runner.h', - 'test/parallel_test_launcher.cc', - 'test/parallel_test_launcher.h', 'test/perf_log.cc', 'test/perf_log.h', 'test/perf_test_suite.cc', 'test/perf_test_suite.h', 'test/perf_time_logger.cc', 'test/perf_time_logger.h', - 'test/perftimer.cc', - 'test/perftimer.h', 'test/power_monitor_test_base.cc', 'test/power_monitor_test_base.h', 'test/scoped_locale.cc', @@ -913,12 +956,11 @@ 'test/task_runner_test_template.h', 'test/test_file_util.cc', 'test/test_file_util.h', + 'test/test_file_util_android.cc', 'test/test_file_util_linux.cc', 'test/test_file_util_mac.cc', 'test/test_file_util_posix.cc', 'test/test_file_util_win.cc', - 'test/test_launcher.cc', - 'test/test_launcher.h', 'test/test_listener_ios.h', 'test/test_listener_ios.mm', 'test/test_pending_task.cc', @@ -945,9 +987,6 @@ 'test/thread_test_helper.h', 'test/trace_event_analyzer.cc', 'test/trace_event_analyzer.h', - 'test/unit_test_launcher.cc', - 'test/unit_test_launcher.h', - 'test/unit_test_launcher_ios.cc', 'test/values_test_util.cc', 'test/values_test_util.h', ], @@ -960,7 +999,7 @@ ], 'sources!': [ # iOS uses its own unit test launcher. - 'test/unit_test_launcher.cc', + 'test/launcher/unit_test_launcher.cc', ], }], ], # target_conditions @@ -1184,6 +1223,8 @@ 'sources': [ 'android/java/src/org/chromium/base/ActivityStatus.java', 'android/java/src/org/chromium/base/BuildInfo.java', + 'android/java/src/org/chromium/base/CommandLine.java', + 'android/java/src/org/chromium/base/ContentUriUtils.java', 'android/java/src/org/chromium/base/CpuFeatures.java', 'android/java/src/org/chromium/base/ImportantFileWriterAndroid.java', 'android/java/src/org/chromium/base/MemoryPressureListener.java', @@ -1204,6 +1245,18 @@ ], 'variables': { 'jni_gen_package': 'base', + 'jni_generator_ptr_type': 'long', + }, + 'includes': [ '../build/jni_generator.gypi' ], + }, + { + 'target_name': 'base_unittests_jni_headers', + 'type': 'none', + 'sources': [ + 'test/android/java/src/org/chromium/base/ContentUriTestUtils.java', + ], + 'variables': { + 'jni_gen_package': 'base', }, 'includes': [ '../build/jni_generator.gypi' ], }, @@ -1227,6 +1280,17 @@ ], }, { + 'target_name': 'base_java_unittest_support', + 'type': 'none', + 'dependencies': [ + 'base_java', + ], + 'variables': { + 'java_in_dir': '../base/test/android/java', + }, + 'includes': [ '../build/java.gypi' ], + }, + { 'target_name': 'base_java_activity_state', 'type': 'none', # This target is used to auto-generate ActivityState.java diff --git a/chromium/base/base.gypi b/chromium/base/base.gypi index 8caee68d5ee..11edab00968 100644 --- a/chromium/base/base.gypi +++ b/chromium/base/base.gypi @@ -39,6 +39,10 @@ 'android/base_jni_registrar.h', 'android/build_info.cc', 'android/build_info.h', + 'android/command_line_android.cc', + 'android/command_line_android.h', + 'android/content_uri_utils.cc', + 'android/content_uri_utils.h', 'android/cpu_features.cc', 'android/fifo_utils.cc', 'android/fifo_utils.h', @@ -108,10 +112,8 @@ 'callback_helpers.h', 'callback_internal.cc', 'callback_internal.h', - 'callback_registry.h', + 'callback_list.h', 'cancelable_callback.h', - 'chromeos/chromeos_version.cc', - 'chromeos/chromeos_version.h', 'command_line.cc', 'command_line.h', 'compiler_specific.h', @@ -179,6 +181,8 @@ 'files/dir_reader_fallback.h', 'files/dir_reader_linux.h', 'files/dir_reader_posix.h', + 'files/file.cc', + 'files/file.h', 'files/file_enumerator.cc', 'files/file_enumerator.h', 'files/file_enumerator_posix.cc', @@ -192,8 +196,10 @@ 'files/file_path_watcher_linux.cc', 'files/file_path_watcher_stub.cc', 'files/file_path_watcher_win.cc', + 'files/file_posix.cc', 'files/file_util_proxy.cc', 'files/file_util_proxy.h', + 'files/file_win.cc', 'files/important_file_writer.h', 'files/important_file_writer.cc', 'files/memory_mapped_file.cc', @@ -285,10 +291,18 @@ 'mac/sdk_forward_declarations.h', 'memory/aligned_memory.cc', 'memory/aligned_memory.h', - 'memory/discardable_memory.cc', 'memory/discardable_memory.h', + 'memory/discardable_memory_allocator_android.cc', + 'memory/discardable_memory_allocator_android.h', 'memory/discardable_memory_android.cc', + 'memory/discardable_memory_android.h', + 'memory/discardable_memory_emulated.cc', + 'memory/discardable_memory_emulated.h', + 'memory/discardable_memory_linux.cc', 'memory/discardable_memory_mac.cc', + 'memory/discardable_memory_provider.cc', + 'memory/discardable_memory_provider.h', + 'memory/discardable_memory_win.cc', 'memory/linked_ptr.h', 'memory/manual_constructor.h', 'memory/memory_pressure_listener.cc', @@ -341,6 +355,8 @@ 'metrics/histogram.h', 'metrics/histogram_base.cc', 'metrics/histogram_base.h', + 'metrics/histogram_delta_serialization.cc', + 'metrics/histogram_delta_serialization.h', 'metrics/histogram_flattener.h', 'metrics/histogram_samples.cc', 'metrics/histogram_samples.h', @@ -534,6 +550,7 @@ 'sys_info_android.cc', 'sys_info_chromeos.cc', 'sys_info_freebsd.cc', + 'sys_info_internal.h', 'sys_info_ios.mm', 'sys_info_linux.cc', 'sys_info_mac.cc', @@ -598,9 +615,11 @@ 'time/time_mac.cc', 'time/time_posix.cc', 'time/time_win.cc', + 'timer/elapsed_timer.cc', + 'timer/elapsed_timer.h', + 'timer/hi_res_timer_manager.h', 'timer/hi_res_timer_manager_posix.cc', 'timer/hi_res_timer_manager_win.cc', - 'timer/hi_res_timer_manager.h', 'timer/timer.cc', 'timer/timer.h', 'tracked_objects.cc', @@ -667,8 +686,17 @@ 'win/windows_version.h', 'win/wrapped_window_proc.cc', 'win/wrapped_window_proc.h', + 'x11/x11_error_tracker.cc', + 'x11/x11_error_tracker.h', + 'x11/x11_error_tracker_gtk.cc', ], 'conditions': [ + ['use_aura==1 and use_x11==1', { + 'sources': [ + 'x11/edid_parser_x11.cc', + 'x11/edid_parser_x11.h', + ], + }], ['google_tv==1', { 'sources': [ 'android/context_types.cc', @@ -686,13 +714,21 @@ 4018, ], 'target_conditions': [ - ['<(use_glib)==0 or >(nacl_untrusted_build)==1', { + ['(<(desktop_linux) == 0 and <(chromeos) == 0) or >(nacl_untrusted_build)==1', { 'sources/': [ ['exclude', '^nix/'], ], 'sources!': [ 'atomicops_internals_x86_gcc.cc', + ], + }], + ['<(use_glib)==0 or >(nacl_untrusted_build)==1', { + 'sources!': [ 'message_loop/message_pump_glib.cc', + ], + }], + ['<(use_x11)==0 or >(nacl_untrusted_build)==1', { + 'sources!': [ 'message_loop/message_pump_x11.cc', ], }], @@ -903,6 +939,11 @@ ['exclude', '^third_party/nspr/'], ], }], + ['<(toolkit_uses_gtk) == 1', { + 'sources!': [ + 'x11/x11_error_tracker.cc', + ], + }], ], }], ], diff --git a/chromium/base/base64.cc b/chromium/base/base64.cc index 9514b0a5c2c..8ed1249257d 100644 --- a/chromium/base/base64.cc +++ b/chromium/base/base64.cc @@ -8,21 +8,15 @@ namespace base { -bool Base64Encode(const StringPiece& input, std::string* output) { +void Base64Encode(const StringPiece& input, std::string* output) { std::string temp; temp.resize(modp_b64_encode_len(input.size())); // makes room for null byte - // null terminates result since result is base64 text! - int input_size = static_cast<int>(input.size()); - // modp_b64_encode_len() returns at least 1, so temp[0] is safe to use. - size_t output_size = modp_b64_encode(&(temp[0]), input.data(), input_size); - if (output_size == MODP_B64_ERROR) - return false; + size_t output_size = modp_b64_encode(&(temp[0]), input.data(), input.size()); temp.resize(output_size); // strips off null byte output->swap(temp); - return true; } bool Base64Decode(const StringPiece& input, std::string* output) { diff --git a/chromium/base/base64.h b/chromium/base/base64.h index cc78edce5c6..43d8f76eb76 100644 --- a/chromium/base/base64.h +++ b/chromium/base/base64.h @@ -12,9 +12,8 @@ namespace base { -// Encodes the input string in base64. Returns true if successful and false -// otherwise. The output string is only modified if successful. -BASE_EXPORT bool Base64Encode(const StringPiece& input, std::string* output); +// Encodes the input string in base64. +BASE_EXPORT void Base64Encode(const StringPiece& input, std::string* output); // Decodes the base64 input string. Returns true if successful and false // otherwise. The output string is only modified if successful. diff --git a/chromium/base/base64_unittest.cc b/chromium/base/base64_unittest.cc index 9a5dd818e06..9b23194ccf6 100644 --- a/chromium/base/base64_unittest.cc +++ b/chromium/base/base64_unittest.cc @@ -16,8 +16,7 @@ TEST(Base64Test, Basic) { std::string decoded; bool ok; - ok = Base64Encode(kText, &encoded); - EXPECT_TRUE(ok); + Base64Encode(kText, &encoded); EXPECT_EQ(kBase64Text, encoded); ok = Base64Decode(encoded, &decoded); diff --git a/chromium/base/base_paths.cc b/chromium/base/base_paths.cc index 9f2b250561f..b4fc28b6c75 100644 --- a/chromium/base/base_paths.cc +++ b/chromium/base/base_paths.cc @@ -24,7 +24,7 @@ bool PathProvider(int key, FilePath* result) { cur = cur.DirName(); break; case DIR_TEMP: - if (!file_util::GetTempDir(&cur)) + if (!base::GetTempDir(&cur)) return false; break; case DIR_TEST_DATA: diff --git a/chromium/base/base_paths_android.cc b/chromium/base/base_paths_android.cc index d0a709fc3a8..68cc5a72cef 100644 --- a/chromium/base/base_paths_android.cc +++ b/chromium/base/base_paths_android.cc @@ -48,7 +48,7 @@ bool PathProviderAndroid(int key, FilePath* result) { case base::DIR_ANDROID_APP_DATA: return base::android::GetDataDirectory(result); case base::DIR_HOME: - *result = file_util::GetHomeDir(); + *result = GetHomeDir(); return true; case base::DIR_ANDROID_EXTERNAL_STORAGE: return base::android::GetExternalStorageDirectory(result); diff --git a/chromium/base/base_paths_mac.mm b/chromium/base/base_paths_mac.mm index 5d4461cb236..86d6a80e9ad 100644 --- a/chromium/base/base_paths_mac.mm +++ b/chromium/base/base_paths_mac.mm @@ -71,7 +71,7 @@ bool PathProviderMac(int key, base::FilePath* result) { #if defined(OS_IOS) // On IOS, this directory does not exist unless it is created explicitly. if (success && !base::PathExists(*result)) - success = file_util::CreateDirectory(*result); + success = base::CreateDirectory(*result); #endif // defined(OS_IOS) return success; } diff --git a/chromium/base/base_paths_posix.cc b/chromium/base/base_paths_posix.cc index 2d5fcbd26d6..b4147e99327 100644 --- a/chromium/base/base_paths_posix.cc +++ b/chromium/base/base_paths_posix.cc @@ -36,7 +36,7 @@ bool PathProviderPosix(int key, FilePath* result) { case base::FILE_MODULE: { // TODO(evanm): is this correct? #if defined(OS_LINUX) FilePath bin_dir; - if (!file_util::ReadSymbolicLink(FilePath(kProcSelfExe), &bin_dir)) { + if (!ReadSymbolicLink(FilePath(kProcSelfExe), &bin_dir)) { NOTREACHED() << "Unable to resolve " << kProcSelfExe << "."; return false; } @@ -110,7 +110,7 @@ bool PathProviderPosix(int key, FilePath* result) { return true; } case base::DIR_HOME: - *result = file_util::GetHomeDir(); + *result = GetHomeDir(); return true; } return false; diff --git a/chromium/base/base_switches.cc b/chromium/base/base_switches.cc index bdd7b62d475..91e401ca497 100644 --- a/chromium/base/base_switches.cc +++ b/chromium/base/base_switches.cc @@ -15,6 +15,11 @@ const char kDebugOnStart[] = "debug-on-start"; // Disables the crash reporting. const char kDisableBreakpad[] = "disable-breakpad"; +// Indicates that crash reporting should be enabled. On platforms where helper +// processes cannot access to files needed to make this decision, this flag is +// generated internally. +const char kEnableCrashReporter[] = "enable-crash-reporter"; + // Enable DCHECKs in release mode. const char kEnableDCHECK[] = "enable-dcheck"; @@ -49,12 +54,27 @@ const char kWaitForDebugger[] = "wait-for-debugger"; // Sends a pretty-printed version of tracing info to the console. const char kTraceToConsole[] = "trace-to-console"; +// Configure whether chrome://profiler will contain timing information. This +// option is enabled by default. A value of "0" will disable profiler timing, +// while all other values will enable it. +const char kProfilerTiming[] = "profiler-timing"; +// Value of the --profiler-timing flag that will disable timing information for +// chrome://profiler. +const char kProfilerTimingDisabledValue[] = "0"; + #if defined(OS_POSIX) -// A flag, generated internally for renderer and other helper process command -// lines on Linux and Mac. It tells the helper process to enable crash dumping -// and reporting, because helpers cannot access the files needed to make this -// decision. -const char kEnableCrashReporter[] = "enable-crash-reporter"; +// Used for turning on Breakpad crash reporting in a debug environment where +// crash reporting is typically compiled but disabled. +const char kEnableCrashReporterForTesting[] = + "enable-crash-reporter-for-testing"; +#endif + +#if defined(OS_ANDROID) +// Overrides low-end device detection, disabling low-end device optimizations. +const char kDisableLowEndDeviceMode[] = "disable-low-end-device-mode"; + +// Overrides low-end device detection, enabling low-end device optimizations. +const char kEnableLowEndDeviceMode[] = "enable-low-end-device-mode"; #endif } // namespace switches diff --git a/chromium/base/base_switches.h b/chromium/base/base_switches.h index 7686e76316e..b679e6d9ebf 100644 --- a/chromium/base/base_switches.h +++ b/chromium/base/base_switches.h @@ -13,17 +13,25 @@ namespace switches { extern const char kDebugOnStart[]; extern const char kDisableBreakpad[]; +extern const char kEnableCrashReporter[]; extern const char kEnableDCHECK[]; extern const char kFullMemoryCrashReport[]; extern const char kNoErrorDialogs[]; +extern const char kProfilerTiming[]; +extern const char kProfilerTimingDisabledValue[]; extern const char kTestChildProcess[]; +extern const char kTraceToConsole[]; extern const char kV[]; extern const char kVModule[]; extern const char kWaitForDebugger[]; -extern const char kTraceToConsole[]; #if defined(OS_POSIX) -extern const char kEnableCrashReporter[]; +extern const char kEnableCrashReporterForTesting[]; +#endif + +#if defined(OS_ANDROID) +extern const char kDisableLowEndDeviceMode[]; +extern const char kEnableLowEndDeviceMode[]; #endif } // namespace switches diff --git a/chromium/base/base_unittests.isolate b/chromium/base/base_unittests.isolate index 0b140dd945b..166cf801eca 100644 --- a/chromium/base/base_unittests.isolate +++ b/chromium/base/base_unittests.isolate @@ -16,8 +16,9 @@ 'command': [ '../testing/xvfb.py', '<(PRODUCT_DIR)', - '../tools/swarm_client/googletest/run_test_cases.py', '<(PRODUCT_DIR)/base_unittests<(EXECUTABLE_SUFFIX)', + '--brave-new-test-launcher', + '--test-launcher-bot-mode', ], 'isolate_dependency_tracked': [ '../testing/xvfb.py', @@ -32,7 +33,7 @@ '<(PRODUCT_DIR)/base_unittests<(EXECUTABLE_SUFFIX)', ], 'isolate_dependency_untracked': [ - '../tools/swarm_client/', + '../tools/swarming_client/', ], }, }], @@ -40,8 +41,9 @@ 'variables': { 'command': [ '../testing/test_env.py', - '../tools/swarm_client/googletest/run_test_cases.py', '<(PRODUCT_DIR)/base_unittests<(EXECUTABLE_SUFFIX)', + '--brave-new-test-launcher', + '--test-launcher-bot-mode', ], }, }], diff --git a/chromium/base/basictypes.h b/chromium/base/basictypes.h index 1bed65c6a51..e77d7b10f24 100644 --- a/chromium/base/basictypes.h +++ b/chromium/base/basictypes.h @@ -9,6 +9,7 @@ #include <stddef.h> // For size_t #include <string.h> // for memcpy +#include "base/compiler_specific.h" #include "base/port.h" // Types that only need exist on certain systems #ifndef COMPILER_MSVC @@ -215,13 +216,21 @@ inline To implicit_cast(From const &f) { // the expression is false, most compilers will issue a warning/error // containing the name of the variable. +#undef COMPILE_ASSERT + +#if __cplusplus >= 201103L + +// Under C++11, just use static_assert. +#define COMPILE_ASSERT(expr, msg) static_assert(expr, #msg) + +#else + template <bool> struct CompileAssert { }; -#undef COMPILE_ASSERT #define COMPILE_ASSERT(expr, msg) \ - typedef CompileAssert<(bool(expr))> msg[bool(expr) ? 1 : -1] + typedef CompileAssert<(bool(expr))> msg[bool(expr) ? 1 : -1] ALLOW_UNUSED // Implementation details of COMPILE_ASSERT: // @@ -264,6 +273,7 @@ struct CompileAssert { // This is to avoid running into a bug in MS VC 7.1, which // causes ((0.0) ? 1 : -1) to incorrectly evaluate to 1. +#endif // bit_cast<Dest,Source> is a template function that implements the // equivalent of "*reinterpret_cast<Dest*>(&source)". We need this in @@ -321,9 +331,7 @@ struct CompileAssert { template <class Dest, class Source> inline Dest bit_cast(const Source& source) { - // Compile time assertion: sizeof(Dest) == sizeof(Source) - // A compile error here means your Dest and Source have different sizes. - typedef char VerifySizesAreEqual [sizeof(Dest) == sizeof(Source) ? 1 : -1]; + COMPILE_ASSERT(sizeof(Dest) == sizeof(Source), VerifySizesAreEqual); Dest dest; memcpy(&dest, &source, sizeof(dest)); diff --git a/chromium/base/callback.h b/chromium/base/callback.h index ade3f8c1fc0..a962957437c 100644 --- a/chromium/base/callback.h +++ b/chromium/base/callback.h @@ -214,11 +214,16 @@ // // PASSING PARAMETERS BY REFERENCE // -// void foo(int arg) { cout << arg << endl } +// Const references are *copied* unless ConstRef is used. Example: +// +// void foo(const int& arg) { printf("%d %p\n", arg, &arg); } // int n = 1; +// base::Closure has_copy = base::Bind(&foo, n); // base::Closure has_ref = base::Bind(&foo, base::ConstRef(n)); // n = 2; -// has_ref.Run(); // Prints "2" +// foo(n); // Prints "2 0xaaaaaaaaaaaa" +// has_copy.Run(); // Prints "1 0xbbbbbbbbbbbb" +// has_ref.Run(); // Prints "2 0xaaaaaaaaaaaa" // // Normally parameters are copied in the closure. DANGER: ConstRef stores a // const reference instead, referencing the original parameter. This means diff --git a/chromium/base/callback_internal.h b/chromium/base/callback_internal.h index 5993824a5a2..e66623cc513 100644 --- a/chromium/base/callback_internal.h +++ b/chromium/base/callback_internal.h @@ -67,6 +67,20 @@ class BASE_EXPORT CallbackBase { InvokeFuncStorage polymorphic_invoke_; }; +// A helper template to determine if given type is non-const move-only-type, +// i.e. if a value of the given type should be passed via .Pass() in a +// destructive way. +template <typename T> struct IsMoveOnlyType { + template <typename U> + static YesType Test(const typename U::MoveOnlyTypeForCPP03*); + + template <typename U> + static NoType Test(...); + + static const bool value = sizeof(Test<T>(0)) == sizeof(YesType) && + !is_const<T>::value; +}; + // This is a typetraits object that's used to take an argument type, and // extract a suitable type for storing and forwarding arguments. // @@ -78,7 +92,7 @@ class BASE_EXPORT CallbackBase { // parameters by const reference. In this case, we end up passing an actual // array type in the initializer list which C++ does not allow. This will // break passing of C-string literals. -template <typename T> +template <typename T, bool is_move_only = IsMoveOnlyType<T>::value> struct CallbackParamTraits { typedef const T& ForwardType; typedef T StorageType; @@ -90,7 +104,7 @@ struct CallbackParamTraits { // // The ForwardType should only be used for unbound arguments. template <typename T> -struct CallbackParamTraits<T&> { +struct CallbackParamTraits<T&, false> { typedef T& ForwardType; typedef T StorageType; }; @@ -101,14 +115,14 @@ struct CallbackParamTraits<T&> { // T[n]" does not seem to match correctly, so we are stuck with this // restriction. template <typename T, size_t n> -struct CallbackParamTraits<T[n]> { +struct CallbackParamTraits<T[n], false> { typedef const T* ForwardType; typedef const T* StorageType; }; // See comment for CallbackParamTraits<T[n]>. template <typename T> -struct CallbackParamTraits<T[]> { +struct CallbackParamTraits<T[], false> { typedef const T* ForwardType; typedef const T* StorageType; }; @@ -126,26 +140,10 @@ struct CallbackParamTraits<T[]> { // correctness, because we are implementing a destructive move. A non-const // reference cannot be used with temporaries which means the result of a // function or a cast would not be usable with Callback<> or Bind(). -// -// TODO(ajwong): We might be able to use SFINAE to search for the existence of -// a Pass() function in the type and avoid the whitelist in CallbackParamTraits -// and CallbackForward. -template <typename T, typename D> -struct CallbackParamTraits<scoped_ptr<T, D> > { - typedef scoped_ptr<T, D> ForwardType; - typedef scoped_ptr<T, D> StorageType; -}; - -template <typename T, typename R> -struct CallbackParamTraits<scoped_ptr_malloc<T, R> > { - typedef scoped_ptr_malloc<T, R> ForwardType; - typedef scoped_ptr_malloc<T, R> StorageType; -}; - template <typename T> -struct CallbackParamTraits<ScopedVector<T> > { - typedef ScopedVector<T> ForwardType; - typedef ScopedVector<T> StorageType; +struct CallbackParamTraits<T, true> { + typedef T ForwardType; + typedef T StorageType; }; // CallbackForward() is a very limited simulation of C++11's std::forward() @@ -165,18 +163,14 @@ struct CallbackParamTraits<ScopedVector<T> > { // parameter to another callback. This is to support Callbacks that return // the movable-but-not-copyable types whitelisted above. template <typename T> -T& CallbackForward(T& t) { return t; } - -template <typename T, typename D> -scoped_ptr<T, D> CallbackForward(scoped_ptr<T, D>& p) { return p.Pass(); } - -template <typename T, typename R> -scoped_ptr_malloc<T, R> CallbackForward(scoped_ptr_malloc<T, R>& p) { - return p.Pass(); +typename enable_if<!IsMoveOnlyType<T>::value, T>::type& CallbackForward(T& t) { + return t; } template <typename T> -ScopedVector<T> CallbackForward(ScopedVector<T>& p) { return p.Pass(); } +typename enable_if<IsMoveOnlyType<T>::value, T>::type CallbackForward(T& t) { + return t.Pass(); +} } // namespace internal } // namespace base diff --git a/chromium/base/callback_list.h b/chromium/base/callback_list.h new file mode 100644 index 00000000000..d12a1e95baa --- /dev/null +++ b/chromium/base/callback_list.h @@ -0,0 +1,384 @@ +// This file was GENERATED by command: +// pump.py callback_list.h.pump +// DO NOT EDIT BY HAND!!! + + +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_CALLBACK_LIST_H_ +#define BASE_CALLBACK_LIST_H_ + +#include <list> + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/callback_internal.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" + +// OVERVIEW: +// +// A container for a list of callbacks. Unlike a normal STL vector or list, +// this container can be modified during iteration without invalidating the +// iterator. It safely handles the case of a callback removing itself +// or another callback from the list while callbacks are being run. +// +// TYPICAL USAGE: +// +// class MyWidget { +// public: +// ... +// +// typedef base::Callback<void(const Foo&)> OnFooCallback; +// +// scoped_ptr<base::CallbackList<void(const Foo&)>::Subscription> +// RegisterCallback(const OnFooCallback& cb) { +// return callback_list_.Add(cb); +// } +// +// private: +// void NotifyFoo(const Foo& foo) { +// callback_list_.Notify(foo); +// } +// +// base::CallbackList<void(const Foo&)> callback_list_; +// +// DISALLOW_COPY_AND_ASSIGN(MyWidget); +// }; +// +// +// class MyWidgetListener { +// public: +// MyWidgetListener::MyWidgetListener() { +// foo_subscription_ = MyWidget::GetCurrent()->RegisterCallback( +// base::Bind(&MyWidgetListener::OnFoo, this))); +// } +// +// MyWidgetListener::~MyWidgetListener() { +// // Subscription gets deleted automatically and will deregister +// // the callback in the process. +// } +// +// private: +// void OnFoo(const Foo& foo) { +// // Do something. +// } +// +// scoped_ptr<base::CallbackList<void(const Foo&)>::Subscription> +// foo_subscription_; +// +// DISALLOW_COPY_AND_ASSIGN(MyWidgetListener); +// }; + +namespace base { + +namespace internal { + +template <typename CallbackType> +class CallbackListBase { + public: + class Subscription { + public: + Subscription(CallbackListBase<CallbackType>* list, + typename std::list<CallbackType>::iterator iter) + : list_(list), + iter_(iter) { + } + + ~Subscription() { + if (list_->active_iterator_count_) + iter_->Reset(); + else + list_->callbacks_.erase(iter_); + } + + private: + CallbackListBase<CallbackType>* list_; + typename std::list<CallbackType>::iterator iter_; + + DISALLOW_COPY_AND_ASSIGN(Subscription); + }; + + // Add a callback to the list. The callback will remain registered until the + // returned Subscription is destroyed, which must occur before the + // CallbackList is destroyed. + scoped_ptr<Subscription> Add(const CallbackType& cb) WARN_UNUSED_RESULT { + DCHECK(!cb.is_null()); + return scoped_ptr<Subscription>( + new Subscription(this, callbacks_.insert(callbacks_.end(), cb))); + } + + protected: + // An iterator class that can be used to access the list of callbacks. + class Iterator { + public: + explicit Iterator(CallbackListBase<CallbackType>* list) + : list_(list), + list_iter_(list_->callbacks_.begin()) { + ++list_->active_iterator_count_; + } + + Iterator(const Iterator& iter) + : list_(iter.list_), + list_iter_(iter.list_iter_) { + ++list_->active_iterator_count_; + } + + ~Iterator() { + if (list_ && --list_->active_iterator_count_ == 0) { + list_->Compact(); + } + } + + CallbackType* GetNext() { + while ((list_iter_ != list_->callbacks_.end()) && list_iter_->is_null()) + ++list_iter_; + + CallbackType* cb = NULL; + if (list_iter_ != list_->callbacks_.end()) { + cb = &(*list_iter_); + ++list_iter_; + } + return cb; + } + + private: + CallbackListBase<CallbackType>* list_; + typename std::list<CallbackType>::iterator list_iter_; + }; + + CallbackListBase() : active_iterator_count_(0) {} + + ~CallbackListBase() { + DCHECK_EQ(0, active_iterator_count_); + DCHECK_EQ(0U, callbacks_.size()); + } + + // Returns an instance of a CallbackListBase::Iterator which can be used + // to run callbacks. + Iterator GetIterator() { + return Iterator(this); + } + + // Compact the list: remove any entries which were NULLed out during + // iteration. + void Compact() { + typename std::list<CallbackType>::iterator it = callbacks_.begin(); + while (it != callbacks_.end()) { + if ((*it).is_null()) + it = callbacks_.erase(it); + else + ++it; + } + } + + private: + std::list<CallbackType> callbacks_; + int active_iterator_count_; + + DISALLOW_COPY_AND_ASSIGN(CallbackListBase); +}; + +} // namespace internal + +template <typename Sig> class CallbackList; + +template <> +class CallbackList<void(void)> + : public internal::CallbackListBase<Callback<void(void)> > { + public: + typedef Callback<void(void)> CallbackType; + + CallbackList() {} + + void Notify() { + internal::CallbackListBase<CallbackType>::Iterator it = + this->GetIterator(); + CallbackType* cb; + while ((cb = it.GetNext()) != NULL) { + cb->Run(); + } + } + + private: + DISALLOW_COPY_AND_ASSIGN(CallbackList); +}; + +template <typename A1> +class CallbackList<void(A1)> + : public internal::CallbackListBase<Callback<void(A1)> > { + public: + typedef Callback<void(A1)> CallbackType; + + CallbackList() {} + + void Notify(typename internal::CallbackParamTraits<A1>::ForwardType a1) { + typename internal::CallbackListBase<CallbackType>::Iterator it = + this->GetIterator(); + CallbackType* cb; + while ((cb = it.GetNext()) != NULL) { + cb->Run(a1); + } + } + + private: + DISALLOW_COPY_AND_ASSIGN(CallbackList); +}; + +template <typename A1, typename A2> +class CallbackList<void(A1, A2)> + : public internal::CallbackListBase<Callback<void(A1, A2)> > { + public: + typedef Callback<void(A1, A2)> CallbackType; + + CallbackList() {} + + void Notify(typename internal::CallbackParamTraits<A1>::ForwardType a1, + typename internal::CallbackParamTraits<A2>::ForwardType a2) { + typename internal::CallbackListBase<CallbackType>::Iterator it = + this->GetIterator(); + CallbackType* cb; + while ((cb = it.GetNext()) != NULL) { + cb->Run(a1, a2); + } + } + + private: + DISALLOW_COPY_AND_ASSIGN(CallbackList); +}; + +template <typename A1, typename A2, typename A3> +class CallbackList<void(A1, A2, A3)> + : public internal::CallbackListBase<Callback<void(A1, A2, A3)> > { + public: + typedef Callback<void(A1, A2, A3)> CallbackType; + + CallbackList() {} + + void Notify(typename internal::CallbackParamTraits<A1>::ForwardType a1, + typename internal::CallbackParamTraits<A2>::ForwardType a2, + typename internal::CallbackParamTraits<A3>::ForwardType a3) { + typename internal::CallbackListBase<CallbackType>::Iterator it = + this->GetIterator(); + CallbackType* cb; + while ((cb = it.GetNext()) != NULL) { + cb->Run(a1, a2, a3); + } + } + + private: + DISALLOW_COPY_AND_ASSIGN(CallbackList); +}; + +template <typename A1, typename A2, typename A3, typename A4> +class CallbackList<void(A1, A2, A3, A4)> + : public internal::CallbackListBase<Callback<void(A1, A2, A3, A4)> > { + public: + typedef Callback<void(A1, A2, A3, A4)> CallbackType; + + CallbackList() {} + + void Notify(typename internal::CallbackParamTraits<A1>::ForwardType a1, + typename internal::CallbackParamTraits<A2>::ForwardType a2, + typename internal::CallbackParamTraits<A3>::ForwardType a3, + typename internal::CallbackParamTraits<A4>::ForwardType a4) { + typename internal::CallbackListBase<CallbackType>::Iterator it = + this->GetIterator(); + CallbackType* cb; + while ((cb = it.GetNext()) != NULL) { + cb->Run(a1, a2, a3, a4); + } + } + + private: + DISALLOW_COPY_AND_ASSIGN(CallbackList); +}; + +template <typename A1, typename A2, typename A3, typename A4, typename A5> +class CallbackList<void(A1, A2, A3, A4, A5)> + : public internal::CallbackListBase<Callback<void(A1, A2, A3, A4, A5)> > { + public: + typedef Callback<void(A1, A2, A3, A4, A5)> CallbackType; + + CallbackList() {} + + void Notify(typename internal::CallbackParamTraits<A1>::ForwardType a1, + typename internal::CallbackParamTraits<A2>::ForwardType a2, + typename internal::CallbackParamTraits<A3>::ForwardType a3, + typename internal::CallbackParamTraits<A4>::ForwardType a4, + typename internal::CallbackParamTraits<A5>::ForwardType a5) { + typename internal::CallbackListBase<CallbackType>::Iterator it = + this->GetIterator(); + CallbackType* cb; + while ((cb = it.GetNext()) != NULL) { + cb->Run(a1, a2, a3, a4, a5); + } + } + + private: + DISALLOW_COPY_AND_ASSIGN(CallbackList); +}; + +template <typename A1, typename A2, typename A3, typename A4, typename A5, + typename A6> +class CallbackList<void(A1, A2, A3, A4, A5, A6)> + : public internal::CallbackListBase<Callback<void(A1, A2, A3, A4, A5, + A6)> > { + public: + typedef Callback<void(A1, A2, A3, A4, A5, A6)> CallbackType; + + CallbackList() {} + + void Notify(typename internal::CallbackParamTraits<A1>::ForwardType a1, + typename internal::CallbackParamTraits<A2>::ForwardType a2, + typename internal::CallbackParamTraits<A3>::ForwardType a3, + typename internal::CallbackParamTraits<A4>::ForwardType a4, + typename internal::CallbackParamTraits<A5>::ForwardType a5, + typename internal::CallbackParamTraits<A6>::ForwardType a6) { + typename internal::CallbackListBase<CallbackType>::Iterator it = + this->GetIterator(); + CallbackType* cb; + while ((cb = it.GetNext()) != NULL) { + cb->Run(a1, a2, a3, a4, a5, a6); + } + } + + private: + DISALLOW_COPY_AND_ASSIGN(CallbackList); +}; + +template <typename A1, typename A2, typename A3, typename A4, typename A5, + typename A6, typename A7> +class CallbackList<void(A1, A2, A3, A4, A5, A6, A7)> + : public internal::CallbackListBase<Callback<void(A1, A2, A3, A4, A5, A6, + A7)> > { + public: + typedef Callback<void(A1, A2, A3, A4, A5, A6, A7)> CallbackType; + + CallbackList() {} + + void Notify(typename internal::CallbackParamTraits<A1>::ForwardType a1, + typename internal::CallbackParamTraits<A2>::ForwardType a2, + typename internal::CallbackParamTraits<A3>::ForwardType a3, + typename internal::CallbackParamTraits<A4>::ForwardType a4, + typename internal::CallbackParamTraits<A5>::ForwardType a5, + typename internal::CallbackParamTraits<A6>::ForwardType a6, + typename internal::CallbackParamTraits<A7>::ForwardType a7) { + typename internal::CallbackListBase<CallbackType>::Iterator it = + this->GetIterator(); + CallbackType* cb; + while ((cb = it.GetNext()) != NULL) { + cb->Run(a1, a2, a3, a4, a5, a6, a7); + } + } + + private: + DISALLOW_COPY_AND_ASSIGN(CallbackList); +}; + +} // namespace base + +#endif // BASE_CALLBACK_LIST_H_ diff --git a/chromium/base/callback_registry.h b/chromium/base/callback_list.h.pump index fcacbf549f2..ea2103ebeef 100644 --- a/chromium/base/callback_registry.h +++ b/chromium/base/callback_list.h.pump @@ -1,14 +1,25 @@ +$$ This is a pump file for generating file templates. Pump is a python +$$ script that is part of the Google Test suite of utilities. Description +$$ can be found here: +$$ +$$ http://code.google.com/p/googletest/wiki/PumpManual +$$ + +$$ See comment for MAX_ARITY in base/bind.h.pump. +$var MAX_ARITY = 7 + // Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef BASE_CALLBACK_REGISTRY_H_ -#define BASE_CALLBACK_REGISTRY_H_ +#ifndef BASE_CALLBACK_LIST_H_ +#define BASE_CALLBACK_LIST_H_ #include <list> #include "base/basictypes.h" #include "base/callback.h" +#include "base/callback_internal.h" #include "base/compiler_specific.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" @@ -28,17 +39,19 @@ // // typedef base::Callback<void(const Foo&)> OnFooCallback; // -// scoped_ptr<base::CallbackRegistry<Foo>::Subscription> RegisterCallback( -// const OnFooCallback& cb) { -// return callback_registry_.Add(cb); +// scoped_ptr<base::CallbackList<void(const Foo&)>::Subscription> +// RegisterCallback(const OnFooCallback& cb) { +// return callback_list_.Add(cb); // } // // private: // void NotifyFoo(const Foo& foo) { -// callback_registry_.Notify(foo); +// callback_list_.Notify(foo); // } // -// base::CallbackRegistry<Foo> callback_registry_; +// base::CallbackList<void(const Foo&)> callback_list_; +// +// DISALLOW_COPY_AND_ASSIGN(MyWidget); // }; // // @@ -59,7 +72,10 @@ // // Do something. // } // -// scoped_ptr<base::CallbackRegistry<Foo>::Subscription> foo_subscription_; +// scoped_ptr<base::CallbackList<void(const Foo&)>::Subscription> +// foo_subscription_; +// +// DISALLOW_COPY_AND_ASSIGN(MyWidgetListener); // }; namespace base { @@ -67,24 +83,25 @@ namespace base { namespace internal { template <typename CallbackType> -class CallbackRegistryBase { +class CallbackListBase { public: class Subscription { public: - Subscription(CallbackRegistryBase<CallbackType>* list, + Subscription(CallbackListBase<CallbackType>* list, typename std::list<CallbackType>::iterator iter) : list_(list), - iter_(iter) {} + iter_(iter) { + } ~Subscription() { if (list_->active_iterator_count_) - (*iter_).Reset(); + iter_->Reset(); else list_->callbacks_.erase(iter_); } private: - CallbackRegistryBase<CallbackType>* list_; + CallbackListBase<CallbackType>* list_; typename std::list<CallbackType>::iterator iter_; DISALLOW_COPY_AND_ASSIGN(Subscription); @@ -92,8 +109,8 @@ class CallbackRegistryBase { // Add a callback to the list. The callback will remain registered until the // returned Subscription is destroyed, which must occur before the - // CallbackRegistry is destroyed. - scoped_ptr<Subscription> Add(const CallbackType& cb) { + // CallbackList is destroyed. + scoped_ptr<Subscription> Add(const CallbackType& cb) WARN_UNUSED_RESULT { DCHECK(!cb.is_null()); return scoped_ptr<Subscription>( new Subscription(this, callbacks_.insert(callbacks_.end(), cb))); @@ -103,7 +120,7 @@ class CallbackRegistryBase { // An iterator class that can be used to access the list of callbacks. class Iterator { public: - explicit Iterator(CallbackRegistryBase<CallbackType>* list) + explicit Iterator(CallbackListBase<CallbackType>* list) : list_(list), list_iter_(list_->callbacks_.begin()) { ++list_->active_iterator_count_; @@ -134,19 +151,18 @@ class CallbackRegistryBase { } private: - CallbackRegistryBase<CallbackType>* list_; + CallbackListBase<CallbackType>* list_; typename std::list<CallbackType>::iterator list_iter_; }; - CallbackRegistryBase() - : active_iterator_count_(0) {} + CallbackListBase() : active_iterator_count_(0) {} - ~CallbackRegistryBase() { + ~CallbackListBase() { DCHECK_EQ(0, active_iterator_count_); DCHECK_EQ(0U, callbacks_.size()); } - // Returns an instance of a CallbackRegistryBase::Iterator which can be used + // Returns an instance of a CallbackListBase::Iterator which can be used // to run callbacks. Iterator GetIterator() { return Iterator(this); @@ -168,49 +184,64 @@ class CallbackRegistryBase { std::list<CallbackType> callbacks_; int active_iterator_count_; - DISALLOW_COPY_AND_ASSIGN(CallbackRegistryBase); + DISALLOW_COPY_AND_ASSIGN(CallbackListBase); }; } // namespace internal -template <typename Details> -class CallbackRegistry - : public internal::CallbackRegistryBase<Callback<void(const Details&)> > { - public: - CallbackRegistry() {} - - // Execute all active callbacks with |details| parameter. - void Notify(const Details& details) { - typename internal::CallbackRegistryBase< - Callback<void(const Details&)> >::Iterator it = this->GetIterator(); - Callback<void(const Details&)>* cb; - while((cb = it.GetNext()) != NULL) { - cb->Run(details); - } - } +template <typename Sig> class CallbackList; -private: - DISALLOW_COPY_AND_ASSIGN(CallbackRegistry); -}; -template <> class CallbackRegistry<void> - : public internal::CallbackRegistryBase<Closure> { +$range ARITY 0..MAX_ARITY +$for ARITY [[ +$range ARG 1..ARITY + +$if ARITY == 0 [[ +template <> +class CallbackList<void(void)> + : public internal::CallbackListBase<Callback<void(void)> > { +]] $else [[ +template <$for ARG , [[typename A$(ARG)]]> +class CallbackList<void($for ARG , [[A$(ARG)]])> + : public internal::CallbackListBase<Callback<void($for ARG , [[A$(ARG)]])> > { +]] + public: - CallbackRegistry() {} - - // Execute all active callbacks. - void Notify() { - Iterator it = this->GetIterator(); - Closure* cb; - while((cb = it.GetNext()) != NULL) { - cb->Run(); +$if ARITY == 0 [[ + + typedef Callback<void(void)> CallbackType; +]] $else [[ + + typedef Callback<void($for ARG , [[A$(ARG)]])> CallbackType; +]] + + + CallbackList() {} + + void Notify($for ARG , + [[typename internal::CallbackParamTraits<A$(ARG)>::ForwardType a$(ARG)]]) { +$if ARITY == 0 [[ + + internal::CallbackListBase<CallbackType>::Iterator it = + this->GetIterator(); +]] $else [[ + + typename internal::CallbackListBase<CallbackType>::Iterator it = + this->GetIterator(); +]] + + CallbackType* cb; + while ((cb = it.GetNext()) != NULL) { + cb->Run($for ARG , [[a$(ARG)]]); } } private: - DISALLOW_COPY_AND_ASSIGN(CallbackRegistry); + DISALLOW_COPY_AND_ASSIGN(CallbackList); }; + +]] $$ for ARITY } // namespace base -#endif // BASE_CALLBACK_REGISTRY_H_ +#endif // BASE_CALLBACK_LIST_H_ diff --git a/chromium/base/callback_list_unittest.cc b/chromium/base/callback_list_unittest.cc new file mode 100644 index 00000000000..9adbabb0931 --- /dev/null +++ b/chromium/base/callback_list_unittest.cc @@ -0,0 +1,291 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/callback_list.h" + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/memory/scoped_ptr.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace { + +class Listener { + public: + Listener() : total_(0), scaler_(1) {} + explicit Listener(int scaler) : total_(0), scaler_(scaler) {} + void IncrementTotal() { total_++; } + void IncrementByMultipleOfScaler(int x) { total_ += x * scaler_; } + + int total() const { return total_; } + + private: + int total_; + int scaler_; + DISALLOW_COPY_AND_ASSIGN(Listener); +}; + +class Remover { + public: + Remover() : total_(0) {} + void IncrementTotalAndRemove() { + total_++; + removal_subscription_.reset(); + } + void SetSubscriptionToRemove( + scoped_ptr<CallbackList<void(void)>::Subscription> sub) { + removal_subscription_ = sub.Pass(); + } + + int total() const { return total_; } + + private: + int total_; + scoped_ptr<CallbackList<void(void)>::Subscription> removal_subscription_; + DISALLOW_COPY_AND_ASSIGN(Remover); +}; + +class Adder { + public: + explicit Adder(CallbackList<void(void)>* cb_reg) + : added_(false), + total_(0), + cb_reg_(cb_reg) { + } + void AddCallback() { + if (!added_) { + added_ = true; + subscription_ = + cb_reg_->Add(Bind(&Adder::IncrementTotal, Unretained(this))); + } + } + void IncrementTotal() { total_++; } + + bool added() const { return added_; } + + int total() const { return total_; } + + private: + bool added_; + int total_; + CallbackList<void(void)>* cb_reg_; + scoped_ptr<CallbackList<void(void)>::Subscription> subscription_; + DISALLOW_COPY_AND_ASSIGN(Adder); +}; + +class Summer { + public: + Summer() : value_(0) {} + + void AddOneParam(int a) { value_ = a; } + void AddTwoParam(int a, int b) { value_ = a + b; } + void AddThreeParam(int a, int b, int c) { value_ = a + b + c; } + void AddFourParam(int a, int b, int c, int d) { value_ = a + b + c + d; } + void AddFiveParam(int a, int b, int c, int d, int e) { + value_ = a + b + c + d + e; + } + void AddSixParam(int a, int b, int c, int d, int e , int f) { + value_ = a + b + c + d + e + f; + } + + int value() const { return value_; } + + private: + int value_; + DISALLOW_COPY_AND_ASSIGN(Summer); +}; + +// Sanity check that we can instantiate a CallbackList for each arity. +TEST(CallbackListTest, ArityTest) { + Summer s; + + CallbackList<void(int)> c1; + scoped_ptr<CallbackList<void(int)>::Subscription> subscription1 = + c1.Add(Bind(&Summer::AddOneParam, Unretained(&s))); + + c1.Notify(1); + EXPECT_EQ(1, s.value()); + + CallbackList<void(int, int)> c2; + scoped_ptr<CallbackList<void(int, int)>::Subscription> subscription2 = + c2.Add(Bind(&Summer::AddTwoParam, Unretained(&s))); + + c2.Notify(1, 2); + EXPECT_EQ(3, s.value()); + + CallbackList<void(int, int, int)> c3; + scoped_ptr<CallbackList<void(int, int, int)>::Subscription> + subscription3 = c3.Add(Bind(&Summer::AddThreeParam, Unretained(&s))); + + c3.Notify(1, 2, 3); + EXPECT_EQ(6, s.value()); + + CallbackList<void(int, int, int, int)> c4; + scoped_ptr<CallbackList<void(int, int, int, int)>::Subscription> + subscription4 = c4.Add(Bind(&Summer::AddFourParam, Unretained(&s))); + + c4.Notify(1, 2, 3, 4); + EXPECT_EQ(10, s.value()); + + CallbackList<void(int, int, int, int, int)> c5; + scoped_ptr<CallbackList<void(int, int, int, int, int)>::Subscription> + subscription5 = c5.Add(Bind(&Summer::AddFiveParam, Unretained(&s))); + + c5.Notify(1, 2, 3, 4, 5); + EXPECT_EQ(15, s.value()); + + CallbackList<void(int, int, int, int, int, int)> c6; + scoped_ptr<CallbackList<void(int, int, int, int, int, int)>::Subscription> + subscription6 = c6.Add(Bind(&Summer::AddSixParam, Unretained(&s))); + + c6.Notify(1, 2, 3, 4, 5, 6); + EXPECT_EQ(21, s.value()); +} + +// Sanity check that closures added to the list will be run, and those removed +// from the list will not be run. +TEST(CallbackListTest, BasicTest) { + CallbackList<void(void)> cb_reg; + Listener a, b, c; + + scoped_ptr<CallbackList<void(void)>::Subscription> a_subscription = + cb_reg.Add(Bind(&Listener::IncrementTotal, Unretained(&a))); + scoped_ptr<CallbackList<void(void)>::Subscription> b_subscription = + cb_reg.Add(Bind(&Listener::IncrementTotal, Unretained(&b))); + + EXPECT_TRUE(a_subscription.get()); + EXPECT_TRUE(b_subscription.get()); + + cb_reg.Notify(); + + EXPECT_EQ(1, a.total()); + EXPECT_EQ(1, b.total()); + + b_subscription.reset(); + + scoped_ptr<CallbackList<void(void)>::Subscription> c_subscription = + cb_reg.Add(Bind(&Listener::IncrementTotal, Unretained(&c))); + + cb_reg.Notify(); + + EXPECT_EQ(2, a.total()); + EXPECT_EQ(1, b.total()); + EXPECT_EQ(1, c.total()); + + a_subscription.reset(); + b_subscription.reset(); + c_subscription.reset(); +} + +// Sanity check that callbacks with details added to the list will be run, with +// the correct details, and those removed from the list will not be run. +TEST(CallbackListTest, BasicTestWithParams) { + CallbackList<void(int)> cb_reg; + Listener a(1), b(-1), c(1); + + scoped_ptr<CallbackList<void(int)>::Subscription> a_subscription = + cb_reg.Add(Bind(&Listener::IncrementByMultipleOfScaler, Unretained(&a))); + scoped_ptr<CallbackList<void(int)>::Subscription> b_subscription = + cb_reg.Add(Bind(&Listener::IncrementByMultipleOfScaler, Unretained(&b))); + + EXPECT_TRUE(a_subscription.get()); + EXPECT_TRUE(b_subscription.get()); + + cb_reg.Notify(10); + + EXPECT_EQ(10, a.total()); + EXPECT_EQ(-10, b.total()); + + b_subscription.reset(); + + scoped_ptr<CallbackList<void(int)>::Subscription> c_subscription = + cb_reg.Add(Bind(&Listener::IncrementByMultipleOfScaler, Unretained(&c))); + + cb_reg.Notify(10); + + EXPECT_EQ(20, a.total()); + EXPECT_EQ(-10, b.total()); + EXPECT_EQ(10, c.total()); + + a_subscription.reset(); + b_subscription.reset(); + c_subscription.reset(); +} + +// Test the a callback can remove itself or a different callback from the list +// during iteration without invalidating the iterator. +TEST(CallbackListTest, RemoveCallbacksDuringIteration) { + CallbackList<void(void)> cb_reg; + Listener a, b; + Remover remover_1, remover_2; + + scoped_ptr<CallbackList<void(void)>::Subscription> remover_1_sub = + cb_reg.Add(Bind(&Remover::IncrementTotalAndRemove, + Unretained(&remover_1))); + scoped_ptr<CallbackList<void(void)>::Subscription> remover_2_sub = + cb_reg.Add(Bind(&Remover::IncrementTotalAndRemove, + Unretained(&remover_2))); + scoped_ptr<CallbackList<void(void)>::Subscription> a_subscription = + cb_reg.Add(Bind(&Listener::IncrementTotal, Unretained(&a))); + scoped_ptr<CallbackList<void(void)>::Subscription> b_subscription = + cb_reg.Add(Bind(&Listener::IncrementTotal, Unretained(&b))); + + // |remover_1| will remove itself. + remover_1.SetSubscriptionToRemove(remover_1_sub.Pass()); + // |remover_2| will remove a. + remover_2.SetSubscriptionToRemove(a_subscription.Pass()); + + cb_reg.Notify(); + + // |remover_1| runs once (and removes itself), |remover_2| runs once (and + // removes a), |a| never runs, and |b| runs once. + EXPECT_EQ(1, remover_1.total()); + EXPECT_EQ(1, remover_2.total()); + EXPECT_EQ(0, a.total()); + EXPECT_EQ(1, b.total()); + + cb_reg.Notify(); + + // Only |remover_2| and |b| run this time. + EXPECT_EQ(1, remover_1.total()); + EXPECT_EQ(2, remover_2.total()); + EXPECT_EQ(0, a.total()); + EXPECT_EQ(2, b.total()); +} + +// Test that a callback can add another callback to the list durning iteration +// without invalidating the iterator. The newly added callback should be run on +// the current iteration as will all other callbacks in the list. +TEST(CallbackListTest, AddCallbacksDuringIteration) { + CallbackList<void(void)> cb_reg; + Adder a(&cb_reg); + Listener b; + scoped_ptr<CallbackList<void(void)>::Subscription> a_subscription = + cb_reg.Add(Bind(&Adder::AddCallback, Unretained(&a))); + scoped_ptr<CallbackList<void(void)>::Subscription> b_subscription = + cb_reg.Add(Bind(&Listener::IncrementTotal, Unretained(&b))); + + cb_reg.Notify(); + + EXPECT_EQ(1, a.total()); + EXPECT_EQ(1, b.total()); + EXPECT_TRUE(a.added()); + + cb_reg.Notify(); + + EXPECT_EQ(2, a.total()); + EXPECT_EQ(2, b.total()); +} + +// Sanity check: notifying an empty list is a no-op. +TEST(CallbackListTest, EmptyList) { + CallbackList<void(void)> cb_reg; + + cb_reg.Notify(); +} + +} // namespace +} // namespace base diff --git a/chromium/base/callback_list_unittest.nc b/chromium/base/callback_list_unittest.nc new file mode 100644 index 00000000000..2d464cf1f1b --- /dev/null +++ b/chromium/base/callback_list_unittest.nc @@ -0,0 +1,51 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/callback_list.h" + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/memory/scoped_ptr.h" + +namespace base { + +class Foo { + public: + Foo() {} + ~Foo() {} +}; + +class FooListener { + public: + FooListener() {} + + void GotAScopedFoo(scoped_ptr<Foo> f) { foo_ = f.Pass(); } + + scoped_ptr<Foo> foo_; + + private: + DISALLOW_COPY_AND_ASSIGN(FooListener); +}; + + +#if defined(NCTEST_MOVE_ONLY_TYPE_PARAMETER) // [r"calling a private constructor of class"] + +// Callbacks run with a move-only typed parameter. +// +// CallbackList does not support move-only typed parameters. Notify() is +// designed to take zero or more parameters, and run each registered callback +// with them. With move-only types, the parameter will be set to NULL after the +// first callback has been run. +void WontCompile() { + FooListener f; + CallbackList<void(scoped_ptr<Foo>)> c1; + scoped_ptr<CallbackList<void(scoped_ptr<Foo>)>::Subscription> sub = + c1.Add(Bind(&FooListener::GotAScopedFoo, Unretained(&f))); + c1.Notify(scoped_ptr<Foo>(new Foo())); +} + +#endif + +} // namespace base diff --git a/chromium/base/callback_registry_unittest.cc b/chromium/base/callback_registry_unittest.cc deleted file mode 100644 index 3459c073f16..00000000000 --- a/chromium/base/callback_registry_unittest.cc +++ /dev/null @@ -1,216 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "base/callback_registry.h" - -#include "base/basictypes.h" -#include "base/bind.h" -#include "base/bind_helpers.h" -#include "base/memory/scoped_ptr.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace base { -namespace { - -class Listener { - public: - Listener() : total_(0), scaler_(1) {} - explicit Listener(int scaler) : total_(0), scaler_(scaler) {} - void IncrementTotal() { total_++; } - void IncrementByMultipleOfScaler(const int& x) { total_ += x * scaler_; } - - int total_; - - private: - int scaler_; - DISALLOW_COPY_AND_ASSIGN(Listener); -}; - -class Remover { - public: - Remover() : total_(0) {} - void IncrementTotalAndRemove() { - total_++; - removal_subscription_.reset(); - } - void SetSubscriptionToRemove( - scoped_ptr<CallbackRegistry<void>::Subscription> sub) { - removal_subscription_ = sub.Pass(); - } - - int total_; - - private: - scoped_ptr<CallbackRegistry<void>::Subscription> removal_subscription_; - DISALLOW_COPY_AND_ASSIGN(Remover); -}; - -class Adder { - public: - explicit Adder(CallbackRegistry<void>* cb_reg) - : added_(false), - total_(0), - cb_reg_(cb_reg) {} - void AddCallback() { - if (!added_) { - added_ = true; - subscription_ = - cb_reg_->Add(Bind(&Adder::IncrementTotal, Unretained(this))); - } - } - void IncrementTotal() { total_++; } - - bool added_; - int total_; - - private: - CallbackRegistry<void>* cb_reg_; - scoped_ptr<CallbackRegistry<void>::Subscription> subscription_; - DISALLOW_COPY_AND_ASSIGN(Adder); -}; - -// Sanity check that closures added to the list will be run, and those removed -// from the list will not be run. -TEST(CallbackRegistryTest, BasicTest) { - CallbackRegistry<void> cb_reg; - Listener a, b, c; - - scoped_ptr<CallbackRegistry<void>::Subscription> a_subscription = - cb_reg.Add(Bind(&Listener::IncrementTotal, Unretained(&a))); - scoped_ptr<CallbackRegistry<void>::Subscription> b_subscription = - cb_reg.Add(Bind(&Listener::IncrementTotal, Unretained(&b))); - - EXPECT_TRUE(a_subscription.get()); - EXPECT_TRUE(b_subscription.get()); - - cb_reg.Notify(); - - EXPECT_EQ(1, a.total_); - EXPECT_EQ(1, b.total_); - - b_subscription.reset(); - - scoped_ptr<CallbackRegistry<void>::Subscription> c_subscription = - cb_reg.Add(Bind(&Listener::IncrementTotal, Unretained(&c))); - - cb_reg.Notify(); - - EXPECT_EQ(2, a.total_); - EXPECT_EQ(1, b.total_); - EXPECT_EQ(1, c.total_); - - a_subscription.reset(); - b_subscription.reset(); - c_subscription.reset(); -} - -// Sanity check that callbacks with details added to the list will be run, with -// the correct details, and those removed from the list will not be run. -TEST(CallbackRegistryTest, BasicTestWithParams) { - CallbackRegistry<int> cb_reg; - Listener a(1), b(-1), c(1); - - scoped_ptr<CallbackRegistry<int>::Subscription> a_subscription = - cb_reg.Add(Bind(&Listener::IncrementByMultipleOfScaler, Unretained(&a))); - scoped_ptr<CallbackRegistry<int>::Subscription> b_subscription = - cb_reg.Add(Bind(&Listener::IncrementByMultipleOfScaler, Unretained(&b))); - - EXPECT_TRUE(a_subscription.get()); - EXPECT_TRUE(b_subscription.get()); - - cb_reg.Notify(10); - - EXPECT_EQ(10, a.total_); - EXPECT_EQ(-10, b.total_); - - b_subscription.reset(); - - scoped_ptr<CallbackRegistry<int>::Subscription> c_subscription = - cb_reg.Add(Bind(&Listener::IncrementByMultipleOfScaler, Unretained(&c))); - - cb_reg.Notify(10); - - EXPECT_EQ(20, a.total_); - EXPECT_EQ(-10, b.total_); - EXPECT_EQ(10, c.total_); - - a_subscription.reset(); - b_subscription.reset(); - c_subscription.reset(); -} - -// Test the a callback can remove itself or a different callback from the list -// during iteration without invalidating the iterator. -TEST(CallbackRegistryTest, RemoveCallbacksDuringIteration) { - CallbackRegistry<void> cb_reg; - Listener a, b; - Remover remover_1, remover_2; - - scoped_ptr<CallbackRegistry<void>::Subscription> remover_1_subscription = - cb_reg.Add(Bind(&Remover::IncrementTotalAndRemove, - Unretained(&remover_1))); - scoped_ptr<CallbackRegistry<void>::Subscription> remover_2_subscription = - cb_reg.Add(Bind(&Remover::IncrementTotalAndRemove, - Unretained(&remover_2))); - scoped_ptr<CallbackRegistry<void>::Subscription> a_subscription = - cb_reg.Add(Bind(&Listener::IncrementTotal, Unretained(&a))); - scoped_ptr<CallbackRegistry<void>::Subscription> b_subscription = - cb_reg.Add(Bind(&Listener::IncrementTotal, Unretained(&b))); - - // |remover_1| will remove itself. - remover_1.SetSubscriptionToRemove(remover_1_subscription.Pass()); - // |remover_2| will remove a. - remover_2.SetSubscriptionToRemove(a_subscription.Pass()); - - cb_reg.Notify(); - - // |remover_1| runs once (and removes itself), |remover_2| runs once (and - // removes a), |a| never runs, and |b| runs once. - EXPECT_EQ(1, remover_1.total_); - EXPECT_EQ(1, remover_2.total_); - EXPECT_EQ(0, a.total_); - EXPECT_EQ(1, b.total_); - - cb_reg.Notify(); - - // Only |remover_2| and |b| run this time. - EXPECT_EQ(1, remover_1.total_); - EXPECT_EQ(2, remover_2.total_); - EXPECT_EQ(0, a.total_); - EXPECT_EQ(2, b.total_); -} - -// Test that a callback can add another callback to the list durning iteration -// without invalidating the iterator. The newly added callback should be run on -// the current iteration as will all other callbacks in the list. -TEST(CallbackRegistryTest, AddCallbacksDuringIteration) { - CallbackRegistry<void> cb_reg; - Adder a(&cb_reg); - Listener b; - scoped_ptr<CallbackRegistry<void>::Subscription> a_subscription = - cb_reg.Add(Bind(&Adder::AddCallback, Unretained(&a))); - scoped_ptr<CallbackRegistry<void>::Subscription> b_subscription = - cb_reg.Add(Bind(&Listener::IncrementTotal, Unretained(&b))); - - cb_reg.Notify(); - - EXPECT_EQ(1, a.total_); - EXPECT_EQ(1, b.total_); - EXPECT_TRUE(a.added_); - - cb_reg.Notify(); - - EXPECT_EQ(2, a.total_); - EXPECT_EQ(2, b.total_); -} - -// Sanity check: notifying an empty list is a no-op. -TEST(CallbackRegistryTest, EmptyList) { - CallbackRegistry<void> cb_reg; - - cb_reg.Notify(); -} - -} // namespace -} // namespace base diff --git a/chromium/base/cancelable_callback.h b/chromium/base/cancelable_callback.h index 8ef01996a93..1f534c3bef0 100644 --- a/chromium/base/cancelable_callback.h +++ b/chromium/base/cancelable_callback.h @@ -9,8 +9,8 @@ // // NOTE: // -// Calling CancellableCallback::Cancel() brings the object back to its natural, -// default-constructed state, i.e., CancellableCallback::callback() will return +// Calling CancelableCallback::Cancel() brings the object back to its natural, +// default-constructed state, i.e., CancelableCallback::callback() will return // a null callback. // // THREAD-SAFETY: diff --git a/chromium/base/chromeos/chromeos_version.cc b/chromium/base/chromeos/chromeos_version.cc deleted file mode 100644 index 4a70cd5312a..00000000000 --- a/chromium/base/chromeos/chromeos_version.cc +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "base/chromeos/chromeos_version.h" - -#include <stdlib.h> -#include <string.h> - -#include "base/logging.h" - -namespace base { -namespace chromeos { - -bool IsRunningOnChromeOS() { - // Check if the user name is chronos. Note that we don't go with - // getuid() + getpwuid_r() as it may end up reading /etc/passwd, which - // can be expensive. - const char* user = getenv("USER"); - return user && strcmp(user, "chronos") == 0; -} - -} // namespace chromeos -} // namespace base diff --git a/chromium/base/chromeos/chromeos_version.h b/chromium/base/chromeos/chromeos_version.h deleted file mode 100644 index 25acd43a9f0..00000000000 --- a/chromium/base/chromeos/chromeos_version.h +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef BASE_CHROMEOS_CHROMEOS_VERSION_H_ -#define BASE_CHROMEOS_CHROMEOS_VERSION_H_ - -#include "base/base_export.h" - -namespace base { -namespace chromeos { - -// Returns true if the browser is running on Chrome OS. -// Useful for implementing stubs for Linux desktop. -BASE_EXPORT bool IsRunningOnChromeOS(); - -} // namespace chromeos -} // namespace base - -#endif // BASE_CHROMEOS_CHROMEOS_VERSION_H_ diff --git a/chromium/base/command_line.cc b/chromium/base/command_line.cc index 36ac88f12c1..e00eee6bd89 100644 --- a/chromium/base/command_line.cc +++ b/chromium/base/command_line.cc @@ -27,17 +27,22 @@ CommandLine* CommandLine::current_process_commandline_ = NULL; namespace { const CommandLine::CharType kSwitchTerminator[] = FILE_PATH_LITERAL("--"); const CommandLine::CharType kSwitchValueSeparator[] = FILE_PATH_LITERAL("="); + // Since we use a lazy match, make sure that longer versions (like "--") are // listed before shorter versions (like "-") of similar prefixes. #if defined(OS_WIN) +// By putting slash last, we can control whether it is treaded as a switch +// value by changing the value of switch_prefix_count to be one less than +// the array size. const CommandLine::CharType* const kSwitchPrefixes[] = {L"--", L"-", L"/"}; #elif defined(OS_POSIX) // Unixes don't use slash as a switch. const CommandLine::CharType* const kSwitchPrefixes[] = {"--", "-"}; #endif +size_t switch_prefix_count = arraysize(kSwitchPrefixes); size_t GetSwitchPrefixLength(const CommandLine::StringType& string) { - for (size_t i = 0; i < arraysize(kSwitchPrefixes); ++i) { + for (size_t i = 0; i < switch_prefix_count; ++i) { CommandLine::StringType prefix(kSwitchPrefixes[i]); if (string.compare(0, prefix.length(), prefix) == 0) return prefix.length(); @@ -169,6 +174,15 @@ CommandLine::CommandLine(const StringVector& argv) CommandLine::~CommandLine() { } +#if defined(OS_WIN) +// static +void CommandLine::set_slash_is_not_a_switch() { + // The last switch prefix should be slash, so adjust the size to skip it. + DCHECK(wcscmp(kSwitchPrefixes[arraysize(kSwitchPrefixes) - 1], L"/") == 0); + switch_prefix_count = arraysize(kSwitchPrefixes) - 1; +} +#endif + // static bool CommandLine::Init(int argc, const char* const* argv) { if (current_process_commandline_) { @@ -307,8 +321,8 @@ FilePath CommandLine::GetSwitchValuePath( CommandLine::StringType CommandLine::GetSwitchValueNative( const std::string& switch_string) const { - SwitchMap::const_iterator result = switches_.end(); - result = switches_.find(LowerASCIIOnWindows(switch_string)); + SwitchMap::const_iterator result = + switches_.find(LowerASCIIOnWindows(switch_string)); return result == switches_.end() ? StringType() : result->second; } diff --git a/chromium/base/command_line.h b/chromium/base/command_line.h index ed46c4f0d13..81bd4b73761 100644 --- a/chromium/base/command_line.h +++ b/chromium/base/command_line.h @@ -53,6 +53,17 @@ class BASE_EXPORT CommandLine { ~CommandLine(); +#if defined(OS_WIN) + // By default this class will treat command-line arguments beginning with + // slashes as switches on Windows, but not other platforms. + // + // If this behavior is inappropriate for your application, you can call this + // function BEFORE initializing the current process' global command line + // object and the behavior will be the same as Posix systems (only hyphens + // begin switches, everything else will be an arg). + static void set_slash_is_not_a_switch(); +#endif + // Initialize the current process CommandLine singleton. On Windows, ignores // its arguments (we instead parse GetCommandLineW() directly) because we // don't trust the CRT's parsing of the command line, but it still must be diff --git a/chromium/base/compiler_specific.h b/chromium/base/compiler_specific.h index 07b680b7844..dc4b2334987 100644 --- a/chromium/base/compiler_specific.h +++ b/chromium/base/compiler_specific.h @@ -68,6 +68,28 @@ #endif // COMPILER_MSVC +// The C++ standard requires that static const members have an out-of-class +// definition (in a single compilation unit), but MSVC chokes on this (when +// language extensions, which are required, are enabled). (You're only likely to +// notice the need for a definition if you take the address of the member or, +// more commonly, pass it to a function that takes it as a reference argument -- +// probably an STL function.) This macro makes MSVC do the right thing. See +// http://msdn.microsoft.com/en-us/library/34h23df8(v=vs.100).aspx for more +// information. Use like: +// +// In .h file: +// struct Foo { +// static const int kBar = 5; +// }; +// +// In .cc file: +// STATIC_CONST_MEMBER_DEFINITION const int Foo::kBar; +#if defined(COMPILER_MSVC) +#define STATIC_CONST_MEMBER_DEFINITION __declspec(selectany) +#else +#define STATIC_CONST_MEMBER_DEFINITION +#endif + // Annotate a variable indicating it's ok if the variable is not used. // (Typically used to silence a compiler warning when the assignment // is important for some other reason.) @@ -119,6 +141,10 @@ #define OVERRIDE override #elif defined(__clang__) #define OVERRIDE override +#elif defined(COMPILER_GCC) && __cplusplus >= 201103 && \ + (__GNUC__ * 10000 + __GNUC_MINOR__ * 100) >= 40700 +// GCC 4.7 supports explicit virtual overrides when C++11 support is enabled. +#define OVERRIDE override #else #define OVERRIDE #endif @@ -133,6 +159,10 @@ #define FINAL sealed #elif defined(__clang__) #define FINAL final +#elif defined(COMPILER_GCC) && __cplusplus >= 201103 && \ + (__GNUC__ * 10000 + __GNUC_MINOR__ * 100) >= 40700 +// GCC 4.7 supports explicit virtual overrides when C++11 support is enabled. +#define FINAL final #else #define FINAL #endif diff --git a/chromium/base/containers/hash_tables.h b/chromium/base/containers/hash_tables.h index 2a2b52f9da6..365b586ba93 100644 --- a/chromium/base/containers/hash_tables.h +++ b/chromium/base/containers/hash_tables.h @@ -103,7 +103,7 @@ DEFINE_TRIVIAL_HASH(unsigned long long); } DEFINE_STRING_HASH(std::string); -DEFINE_STRING_HASH(string16); +DEFINE_STRING_HASH(base::string16); #undef DEFINE_STRING_HASH diff --git a/chromium/base/containers/mru_cache.h b/chromium/base/containers/mru_cache.h index e59e9098391..15ea2fccdc4 100644 --- a/chromium/base/containers/mru_cache.h +++ b/chromium/base/containers/mru_cache.h @@ -123,8 +123,6 @@ class MRUCacheBase { // Retrieves the payload associated with a given key and returns it via // result without affecting the ordering (unlike Get). - // - // TODO(brettw) We may want a const version of this function in the future. iterator Peek(const KeyType& key) { typename KeyIndex::const_iterator index_iter = index_.find(key); if (index_iter == index_.end()) @@ -132,6 +130,13 @@ class MRUCacheBase { return index_iter->second; } + const_iterator Peek(const KeyType& key) const { + typename KeyIndex::const_iterator index_iter = index_.find(key); + if (index_iter == index_.end()) + return end(); + return index_iter->second; + } + // Erases the item referenced by the given iterator. An iterator to the item // following it will be returned. The iterator must be valid. iterator Erase(iterator pos) { diff --git a/chromium/base/cpu.cc b/chromium/base/cpu.cc index 1761529e046..66207a1e0b8 100644 --- a/chromium/base/cpu.cc +++ b/chromium/base/cpu.cc @@ -8,11 +8,13 @@ #include <algorithm> +#include "base/basictypes.h" #include "build/build_config.h" #if defined(ARCH_CPU_X86_FAMILY) #if defined(_MSC_VER) #include <intrin.h> +#include <immintrin.h> // For _xgetbv() #endif #endif @@ -33,11 +35,16 @@ CPU::CPU() has_ssse3_(false), has_sse41_(false), has_sse42_(false), + has_avx_(false), + has_avx_hardware_(false), + has_aesni_(false), has_non_stop_time_stamp_counter_(false), cpu_vendor_("unknown") { Initialize(); } +namespace { + #if defined(ARCH_CPU_X86_FAMILY) #ifndef _MSC_VER @@ -53,16 +60,6 @@ void __cpuid(int cpu_info[4], int info_type) { ); } -void __cpuidex(int cpu_info[4], int info_type, int info_index) { - __asm__ volatile ( - "mov %%ebx, %%edi\n" - "cpuid\n" - "xchg %%edi, %%ebx\n" - : "=a"(cpu_info[0]), "=D"(cpu_info[1]), "=c"(cpu_info[2]), "=d"(cpu_info[3]) - : "a"(info_type), "c"(info_index) - ); -} - #else void __cpuid(int cpu_info[4], int info_type) { @@ -73,18 +70,22 @@ void __cpuid(int cpu_info[4], int info_type) { ); } -void __cpuidex(int cpu_info[4], int info_type, int info_index) { - __asm__ volatile ( - "cpuid \n\t" - : "=a"(cpu_info[0]), "=b"(cpu_info[1]), "=c"(cpu_info[2]), "=d"(cpu_info[3]) - : "a"(info_type), "c"(info_index) - ); +#endif + +// _xgetbv returns the value of an Intel Extended Control Register (XCR). +// Currently only XCR0 is defined by Intel so |xcr| should always be zero. +uint64 _xgetbv(uint32 xcr) { + uint32 eax, edx; + + __asm__ volatile ("xgetbv" : "=a" (eax), "=d" (edx) : "c" (xcr)); + return (static_cast<uint64>(edx) << 32) | eax; } -#endif -#endif // _MSC_VER +#endif // !_MSC_VER #endif // ARCH_CPU_X86_FAMILY +} // anonymous namespace + void CPU::Initialize() { #if defined(ARCH_CPU_X86_FAMILY) int cpu_info[4] = {-1}; @@ -113,14 +114,25 @@ void CPU::Initialize() { type_ = (cpu_info[0] >> 12) & 0x3; ext_model_ = (cpu_info[0] >> 16) & 0xf; ext_family_ = (cpu_info[0] >> 20) & 0xff; - has_mmx_ = (cpu_info[3] & 0x00800000) != 0; - has_sse_ = (cpu_info[3] & 0x02000000) != 0; - has_sse2_ = (cpu_info[3] & 0x04000000) != 0; - has_sse3_ = (cpu_info[2] & 0x00000001) != 0; + has_mmx_ = (cpu_info[3] & 0x00800000) != 0; + has_sse_ = (cpu_info[3] & 0x02000000) != 0; + has_sse2_ = (cpu_info[3] & 0x04000000) != 0; + has_sse3_ = (cpu_info[2] & 0x00000001) != 0; has_ssse3_ = (cpu_info[2] & 0x00000200) != 0; has_sse41_ = (cpu_info[2] & 0x00080000) != 0; has_sse42_ = (cpu_info[2] & 0x00100000) != 0; - has_avx_ = (cpu_info[2] & 0x10000000) != 0; + has_avx_hardware_ = + (cpu_info[2] & 0x10000000) != 0; + // AVX instructions will generate an illegal instruction exception unless + // a) they are supported by the CPU, + // b) XSAVE is supported by the CPU and + // c) XSAVE is enabled by the kernel. + // See http://software.intel.com/en-us/blogs/2011/04/14/is-avx-enabled + has_avx_ = + has_avx_hardware_ && + (cpu_info[2] & 0x08000000) != 0 /* OSXSAVE */ && + (_xgetbv(0) & 6) == 6 /* XSAVE enabled by kernel */; + has_aesni_ = (cpu_info[2] & 0x02000000) != 0; } // Get the brand string of the cpu. @@ -145,6 +157,13 @@ void CPU::Initialize() { __cpuid(cpu_info, parameter_containing_non_stop_time_stamp_counter); has_non_stop_time_stamp_counter_ = (cpu_info[3] & (1 << 8)) != 0; } +#elif defined(ARCH_CPU_ARM_FAMILY) + // TODO(piman): Expand this. ARM has a CPUID register, but it's not available + // in user mode. /proc/cpuinfo has some information, but it's non standard, + // platform-specific, and not accessible from the sandbox. + // For some purposes, this first approximation is enough. + // crbug.com/313454 + cpu_brand_.assign("ARM"); #endif } diff --git a/chromium/base/cpu.h b/chromium/base/cpu.h index 509763e170f..595ed97b7ce 100644 --- a/chromium/base/cpu.h +++ b/chromium/base/cpu.h @@ -46,6 +46,13 @@ class BASE_EXPORT CPU { bool has_sse41() const { return has_sse41_; } bool has_sse42() const { return has_sse42_; } bool has_avx() const { return has_avx_; } + // has_avx_hardware returns true when AVX is present in the CPU. This might + // differ from the value of |has_avx()| because |has_avx()| also tests for + // operating system support needed to actually call AVX instuctions. + // Note: you should never need to call this function. It was added in order + // to workaround a bug in NSS but |has_avx()| is what you want. + bool has_avx_hardware() const { return has_avx_hardware_; } + bool has_aesni() const { return has_aesni_; } bool has_non_stop_time_stamp_counter() const { return has_non_stop_time_stamp_counter_; } @@ -71,6 +78,8 @@ class BASE_EXPORT CPU { bool has_sse41_; bool has_sse42_; bool has_avx_; + bool has_avx_hardware_; + bool has_aesni_; bool has_non_stop_time_stamp_counter_; std::string cpu_vendor_; std::string cpu_brand_; diff --git a/chromium/base/debug/crash_logging.cc b/chromium/base/debug/crash_logging.cc index 6a36a916c62..caf10b49b07 100644 --- a/chromium/base/debug/crash_logging.cc +++ b/chromium/base/debug/crash_logging.cc @@ -46,14 +46,13 @@ const size_t kLargestValueAllowed = 1024; void SetCrashKeyValue(const base::StringPiece& key, const base::StringPiece& value) { - if (!g_set_key_func_) + if (!g_set_key_func_ || !g_crash_keys_) return; const CrashKey* crash_key = LookupCrashKey(key); - // TODO(rsesek): Do this: - //DCHECK(crash_key) << "All crash keys must be registered before use " - // << "(key = " << key << ")"; + DCHECK(crash_key) << "All crash keys must be registered before use " + << "(key = " << key << ")"; // Handle the un-chunked case. if (!crash_key || crash_key->max_length <= g_chunk_max_length_) { @@ -78,7 +77,7 @@ void SetCrashKeyValue(const base::StringPiece& key, } void ClearCrashKey(const base::StringPiece& key) { - if (!g_clear_key_func_) + if (!g_clear_key_func_ || !g_crash_keys_) return; const CrashKey* crash_key = LookupCrashKey(key); @@ -163,6 +162,8 @@ size_t InitCrashKeys(const CrashKey* const keys, size_t count, } const CrashKey* LookupCrashKey(const base::StringPiece& key) { + if (!g_crash_keys_) + return NULL; CrashKeyMap::const_iterator it = g_crash_keys_->find(key.as_string()); if (it == g_crash_keys_->end()) return NULL; diff --git a/chromium/base/debug/debugger_posix.cc b/chromium/base/debug/debugger_posix.cc index 066c592bd45..60ad5218308 100644 --- a/chromium/base/debug/debugger_posix.cc +++ b/chromium/base/debug/debugger_posix.cc @@ -157,7 +157,7 @@ bool BeingDebugged() { char buf[1024]; ssize_t num_read = HANDLE_EINTR(read(status_fd, buf, sizeof(buf))); - if (HANDLE_EINTR(close(status_fd)) < 0) + if (IGNORE_EINTR(close(status_fd)) < 0) return false; if (num_read <= 0) @@ -195,9 +195,18 @@ bool BeingDebugged() { // +-------+-----------------+-----------------+ // // Thus we do the following: -// Linux: Debug mode, send SIGTRAP; Release mode, send SIGABRT. +// Linux: Debug mode if a debugger is attached, send SIGTRAP; otherwise send +// SIGABRT // Mac: Always send SIGTRAP. +#if defined(ARCH_CPU_ARM_FAMILY) +#define DEBUG_BREAK_ASM() asm("bkpt 0") +#elif defined(ARCH_CPU_MIPS_FAMILY) +#define DEBUG_BREAK_ASM() asm("break 2") +#elif defined(ARCH_CPU_X86_FAMILY) +#define DEBUG_BREAK_ASM() asm("int3") +#endif + #if defined(NDEBUG) && !defined(OS_MACOSX) && !defined(OS_ANDROID) #define DEBUG_BREAK() abort() #elif defined(OS_NACL) @@ -205,7 +214,7 @@ bool BeingDebugged() { // should ask for advice from some NaCl experts about the optimum thing here. // http://code.google.com/p/nativeclient/issues/detail?id=645 #define DEBUG_BREAK() abort() -#elif defined(OS_ANDROID) +#elif !defined(OS_MACOSX) // Though Android has a "helpful" process called debuggerd to catch native // signals on the general assumption that they are fatal errors. If no debugger // is attached, we call abort since Breakpad needs SIGABRT to create a dump. @@ -214,13 +223,17 @@ bool BeingDebugged() { // difficulty continuing in a debugger once we stop from SIG triggered by native // code, use GDB to set |go| to 1 to resume execution; for X86 platform, use // "int3" to setup breakpiont and raise SIGTRAP. +// +// On other POSIX architectures, except Mac OS X, we use the same logic to +// ensure that breakpad creates a dump on crashes while it is still possible to +// use a debugger. namespace { void DebugBreak() { if (!BeingDebugged()) { abort(); } else { -#if defined(ARCH_CPU_X86_FAMILY) - asm("int3"); +#if defined(DEBUG_BREAK_ASM) + DEBUG_BREAK_ASM(); #else volatile int go = 0; while (!go) { @@ -231,13 +244,10 @@ void DebugBreak() { } } // namespace #define DEBUG_BREAK() DebugBreak() -#elif defined(ARCH_CPU_ARM_FAMILY) -// ARM && !ANDROID -#define DEBUG_BREAK() asm("bkpt 0") -#elif defined(ARCH_CPU_MIPS_FAMILY) -#define DEBUG_BREAK() asm("break 2") +#elif defined(DEBUG_BREAK_ASM) +#define DEBUG_BREAK() DEBUG_BREAK_ASM() #else -#define DEBUG_BREAK() asm("int3") +#error "Don't know how to debug break on this architecture/OS" #endif void BreakDebugger() { diff --git a/chromium/base/debug/leak_annotations.h b/chromium/base/debug/leak_annotations.h index 86e8ea911ce..27fe663bddb 100644 --- a/chromium/base/debug/leak_annotations.h +++ b/chromium/base/debug/leak_annotations.h @@ -5,40 +5,30 @@ #ifndef BASE_DEBUG_LEAK_ANNOTATIONS_H_ #define BASE_DEBUG_LEAK_ANNOTATIONS_H_ +#include "base/basictypes.h" #include "build/build_config.h" // This file defines macros which can be used to annotate intentional memory -// leaks. Support for annotations is implemented in HeapChecker and -// LeakSanitizer. Annotated objects will be treated as a source of live -// pointers, i.e. any heap objects reachable by following pointers from an -// annotated object will not be reported as leaks. +// leaks. Support for annotations is implemented in LeakSanitizer. Annotated +// objects will be treated as a source of live pointers, i.e. any heap objects +// reachable by following pointers from an annotated object will not be +// reported as leaks. // // ANNOTATE_SCOPED_MEMORY_LEAK: all allocations made in the current scope // will be annotated as leaks. // ANNOTATE_LEAKING_OBJECT_PTR(X): the heap object referenced by pointer X will // be annotated as a leak. -// -// Note that HeapChecker will report a fatal error if an object which has been -// annotated with ANNOTATE_LEAKING_OBJECT_PTR is later deleted (but -// LeakSanitizer won't). - -#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_NACL) && \ - defined(USE_HEAPCHECKER) -#include "third_party/tcmalloc/chromium/src/gperftools/heap-checker.h" - -#define ANNOTATE_SCOPED_MEMORY_LEAK \ - HeapLeakChecker::Disabler heap_leak_checker_disabler; static_cast<void>(0) - -#define ANNOTATE_LEAKING_OBJECT_PTR(X) \ - HeapLeakChecker::IgnoreObject(X) - -#elif defined(LEAK_SANITIZER) && !defined(OS_NACL) +#if defined(LEAK_SANITIZER) && !defined(OS_NACL) +// Public LSan API from <sanitizer/lsan_interface.h>. extern "C" { void __lsan_disable(); void __lsan_enable(); void __lsan_ignore_object(const void *p); + +// Invoke leak detection immediately. If leaks are found, the process will exit. +void __lsan_do_leak_check(); } // extern "C" class ScopedLeakSanitizerDisabler { diff --git a/chromium/base/debug/profiler.cc b/chromium/base/debug/profiler.cc index 096e343e00e..ed553cdf293 100644 --- a/chromium/base/debug/profiler.cc +++ b/chromium/base/debug/profiler.cc @@ -14,14 +14,16 @@ #include "base/win/pe_image.h" #endif // defined(OS_WIN) -#if defined(ENABLE_PROFILING) && !defined(NO_TCMALLOC) +// TODO(peria): Enable profiling on Windows. +#if defined(ENABLE_PROFILING) && !defined(NO_TCMALLOC) && !defined(OS_WIN) #include "third_party/tcmalloc/chromium/src/gperftools/profiler.h" #endif namespace base { namespace debug { -#if defined(ENABLE_PROFILING) && !defined(NO_TCMALLOC) +// TODO(peria): Enable profiling on Windows. +#if defined(ENABLE_PROFILING) && !defined(NO_TCMALLOC) && !defined(OS_WIN) static int profile_count = 0; diff --git a/chromium/base/debug/stack_trace_android.cc b/chromium/base/debug/stack_trace_android.cc index ec0508a158c..257e82309e7 100644 --- a/chromium/base/debug/stack_trace_android.cc +++ b/chromium/base/debug/stack_trace_android.cc @@ -26,16 +26,6 @@ struct StackCrawlState { bool have_skipped_self; }; -// Clang's unwind.h doesn't provide _Unwind_GetIP on ARM, refer to -// http://llvm.org/bugs/show_bug.cgi?id=16564 for details. -#if defined(__clang__) -uintptr_t _Unwind_GetIP(_Unwind_Context* context) { - uintptr_t ip = 0; - _Unwind_VRS_Get(context, _UVRSC_CORE, 15, _UVRSD_UINT32, &ip); - return ip & ~static_cast<uintptr_t>(0x1); // Remove thumb mode bit. -} -#endif - _Unwind_Reason_Code TraceStackFrame(_Unwind_Context* context, void* arg) { StackCrawlState* state = static_cast<StackCrawlState*>(arg); uintptr_t ip = _Unwind_GetIP(context); diff --git a/chromium/base/debug/stack_trace_posix.cc b/chromium/base/debug/stack_trace_posix.cc index eb5ee08a65e..ed1a91889b1 100644 --- a/chromium/base/debug/stack_trace_posix.cc +++ b/chromium/base/debug/stack_trace_posix.cc @@ -43,6 +43,7 @@ namespace { volatile sig_atomic_t in_signal_handler = 0; +#if !defined(USE_SYMBOLIZE) && defined(__GLIBCXX__) // The prefix used for mangled symbols, per the Itanium C++ ABI: // http://www.codesourcery.com/cxx-abi/abi.html#mangling const char kMangledSymbolPrefix[] = "_Z"; @@ -51,6 +52,7 @@ const char kMangledSymbolPrefix[] = "_Z"; // (('a'..'z').to_a+('A'..'Z').to_a+('0'..'9').to_a + ['_']).join const char kSymbolCharacters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; +#endif // !defined(USE_SYMBOLIZE) && defined(__GLIBCXX__) #if !defined(USE_SYMBOLIZE) // Demangles C++ symbols in the given text. Example: @@ -177,6 +179,7 @@ void PrintToStderr(const char* output) { ignore_result(HANDLE_EINTR(write(STDERR_FILENO, output, strlen(output)))); } +#if !defined(OS_IOS) void StackDumpSignalHandler(int signal, siginfo_t* info, void* void_context) { // NOTE: This code MUST be async-signal safe. // NO malloc or stdio is allowed here. @@ -369,6 +372,7 @@ void StackDumpSignalHandler(int signal, siginfo_t* info, void* void_context) { #endif // defined(OS_MACOSX) _exit(1); } +#endif // !defined(OS_IOS) class PrintBacktraceOutputHandler : public BacktraceOutputHandler { public: @@ -399,6 +403,7 @@ class StreamBacktraceOutputHandler : public BacktraceOutputHandler { DISALLOW_COPY_AND_ASSIGN(StreamBacktraceOutputHandler); }; +#if !defined(OS_IOS) void WarmUpBacktrace() { // Warm up stack trace infrastructure. It turns out that on the first // call glibc initializes some internal data structures using pthread_once, @@ -431,6 +436,7 @@ void WarmUpBacktrace() { // #22 <signal handler called> StackTrace stack_trace; } +#endif // !defined(OS_IOS) } // namespace diff --git a/chromium/base/debug/trace_event.h b/chromium/base/debug/trace_event.h index 6b45652b0d3..18feb33f3d0 100644 --- a/chromium/base/debug/trace_event.h +++ b/chromium/base/debug/trace_event.h @@ -133,7 +133,7 @@ // "arg1", std::string("string will be copied")); // // -// Convertible notes: +// Convertable notes: // Converting a large data type to a string can be costly. To help with this, // the trace framework provides an interface ConvertableToTraceFormat. If you // inherit from it and implement the AppendAsTraceFormat method the trace @@ -144,17 +144,16 @@ // class MyData : public base::debug::ConvertableToTraceFormat { // public: // MyData() {} -// virtual ~MyData() {} // virtual void AppendAsTraceFormat(std::string* out) const OVERRIDE { // out->append("{\"foo\":1}"); // } // private: +// virtual ~MyData() {} // DISALLOW_COPY_AND_ASSIGN(MyData); // }; // -// scoped_ptr<MyData> data(new MyData()); // TRACE_EVENT1("foo", "bar", "data", -// data.PassAs<base::debug::ConvertableToTraceFormat>()); +// scoped_refptr<ConvertableToTraceFormat>(new MyData())); // // The trace framework will take ownership if the passed pointer and it will // be free'd when the trace buffer is flushed. @@ -521,12 +520,18 @@ // all match. |id| must either be a pointer or an integer value up to 64 bits. // If it's a pointer, the bits will be xored with a hash of the process ID so // that the same pointer on two different processes will not collide. +// // An asynchronous operation can consist of multiple phases. The first phase is // defined by the ASYNC_BEGIN calls. Additional phases can be defined using the -// ASYNC_STEP macros. When the operation completes, call ASYNC_END. -// An ASYNC trace typically occur on a single thread (if not, they will only be +// ASYNC_STEP_INTO or ASYNC_STEP_PAST macros. The ASYNC_STEP_INTO macro will +// annotate the block following the call. The ASYNC_STEP_PAST macro will +// annotate the block prior to the call. Note that any particular event must use +// only STEP_INTO or STEP_PAST macros; they can not mix and match. When the +// operation completes, call ASYNC_END. +// +// An ASYNC trace typically occurs on a single thread (if not, they will only be // drawn on the thread defined in the ASYNC_BEGIN event), but all events in that -// operation must use the same |name| and |id|. Each event can have its own +// operation must use the same |name| and |id|. Each step can have its own // args. #define TRACE_EVENT_ASYNC_BEGIN0(category_group, name, id) \ INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_BEGIN, \ @@ -554,27 +559,34 @@ category_group, name, id, TRACE_EVENT_FLAG_COPY, \ arg1_name, arg1_val, arg2_name, arg2_val) -// Records a single ASYNC_STEP event for |step| immediately. If the category -// is not enabled, then this does nothing. The |name| and |id| must match the -// ASYNC_BEGIN event above. The |step| param identifies this step within the -// async event. This should be called at the beginning of the next phase of an -// asynchronous operation. -#define TRACE_EVENT_ASYNC_STEP0(category_group, name, id, step) \ - INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_STEP, \ +// Records a single ASYNC_STEP_INTO event for |step| immediately. If the +// category is not enabled, then this does nothing. The |name| and |id| must +// match the ASYNC_BEGIN event above. The |step| param identifies this step +// within the async event. This should be called at the beginning of the next +// phase of an asynchronous operation. The ASYNC_BEGIN event must not have any +// ASYNC_STEP_PAST events. +#define TRACE_EVENT_ASYNC_STEP_INTO0(category_group, name, id, step) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_STEP_INTO, \ category_group, name, id, TRACE_EVENT_FLAG_NONE, "step", step) -#define TRACE_EVENT_ASYNC_STEP1(category_group, name, id, step, \ - arg1_name, arg1_val) \ - INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_STEP, \ +#define TRACE_EVENT_ASYNC_STEP_INTO1(category_group, name, id, step, \ + arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_STEP_INTO, \ category_group, name, id, TRACE_EVENT_FLAG_NONE, "step", step, \ arg1_name, arg1_val) -#define TRACE_EVENT_COPY_ASYNC_STEP0(category_group, name, id, step) \ - INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_STEP, \ - category_group, name, id, TRACE_EVENT_FLAG_COPY, "step", step) -#define TRACE_EVENT_COPY_ASYNC_STEP1(category_group, name, id, step, \ - arg1_name, arg1_val) \ - INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_STEP, \ - category_group, name, id, TRACE_EVENT_FLAG_COPY, "step", step, \ +// Records a single ASYNC_STEP_PAST event for |step| immediately. If the +// category is not enabled, then this does nothing. The |name| and |id| must +// match the ASYNC_BEGIN event above. The |step| param identifies this step +// within the async event. This should be called at the beginning of the next +// phase of an asynchronous operation. The ASYNC_BEGIN event must not have any +// ASYNC_STEP_INTO events. +#define TRACE_EVENT_ASYNC_STEP_PAST0(category_group, name, id, step) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_STEP_PAST, \ + category_group, name, id, TRACE_EVENT_FLAG_NONE, "step", step) +#define TRACE_EVENT_ASYNC_STEP_PAST1(category_group, name, id, step, \ + arg1_name, arg1_val) \ + INTERNAL_TRACE_EVENT_ADD_WITH_ID(TRACE_EVENT_PHASE_ASYNC_STEP_PAST, \ + category_group, name, id, TRACE_EVENT_FLAG_NONE, "step", step, \ arg1_name, arg1_val) // Records a single ASYNC_END event for "name" immediately. If the category @@ -718,7 +730,7 @@ #define TRACE_EVENT_CATEGORY_GROUP_ENABLED(category_group, ret) \ do { \ INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group); \ - if (*INTERNAL_TRACE_EVENT_UID(catstatic)) { \ + if (*INTERNAL_TRACE_EVENT_UID(category_group_enabled)) { \ *ret = true; \ } else { \ *ret = false; \ @@ -764,7 +776,7 @@ base::debug::TraceLog::GetInstance()->GetNumTracesRecorded // Add a trace event to the platform tracing system. -// void TRACE_EVENT_API_ADD_TRACE_EVENT( +// base::debug::TraceEventHandle TRACE_EVENT_API_ADD_TRACE_EVENT( // char phase, // const unsigned char* category_group_enabled, // const char* name, @@ -778,7 +790,7 @@ base::debug::TraceLog::GetInstance()->AddTraceEvent // Add a trace event to the platform tracing system. -// void TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_TIMESTAMP( +// base::debug::TraceEventHandle TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_TIMESTAMP( // char phase, // const unsigned char* category_group_enabled, // const char* name, @@ -793,6 +805,14 @@ #define TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_THREAD_ID_AND_TIMESTAMP \ base::debug::TraceLog::GetInstance()->AddTraceEventWithThreadIdAndTimestamp +// Set the duration field of a COMPLETE trace event. +// void TRACE_EVENT_API_UPDATE_TRACE_EVENT_DURATION( +// const unsigned char* category_group_enabled, +// const char* name, +// base::debug::TraceEventHandle id) +#define TRACE_EVENT_API_UPDATE_TRACE_EVENT_DURATION \ + base::debug::TraceLog::GetInstance()->UpdateTraceEventDuration + // Defines atomic operations used internally by the tracing system. #define TRACE_EVENT_API_ATOMIC_WORD base::subtle::AtomicWord #define TRACE_EVENT_API_ATOMIC_LOAD(var) base::subtle::NoBarrier_Load(&(var)) @@ -825,27 +845,34 @@ TRACE_EVENT_API_CLASS_EXPORT extern \ // No barriers are needed, because this code is designed to operate safely // even when the unsigned char* points to garbage data (which may be the case // on processors without cache coherency). -#define INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group) \ - static TRACE_EVENT_API_ATOMIC_WORD INTERNAL_TRACE_EVENT_UID(atomic) = 0; \ - const unsigned char* INTERNAL_TRACE_EVENT_UID(catstatic) = \ +#define INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO_CUSTOM_VARIABLES( \ + category_group, atomic, category_group_enabled) \ + category_group_enabled = \ reinterpret_cast<const unsigned char*>(TRACE_EVENT_API_ATOMIC_LOAD( \ - INTERNAL_TRACE_EVENT_UID(atomic))); \ - if (!INTERNAL_TRACE_EVENT_UID(catstatic)) { \ - INTERNAL_TRACE_EVENT_UID(catstatic) = \ + atomic)); \ + if (!category_group_enabled) { \ + category_group_enabled = \ TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED(category_group); \ - TRACE_EVENT_API_ATOMIC_STORE(INTERNAL_TRACE_EVENT_UID(atomic), \ + TRACE_EVENT_API_ATOMIC_STORE(atomic, \ reinterpret_cast<TRACE_EVENT_API_ATOMIC_WORD>( \ - INTERNAL_TRACE_EVENT_UID(catstatic))); \ + category_group_enabled)); \ } +#define INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group) \ + static TRACE_EVENT_API_ATOMIC_WORD INTERNAL_TRACE_EVENT_UID(atomic) = 0; \ + const unsigned char* INTERNAL_TRACE_EVENT_UID(category_group_enabled); \ + INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO_CUSTOM_VARIABLES(category_group, \ + INTERNAL_TRACE_EVENT_UID(atomic), \ + INTERNAL_TRACE_EVENT_UID(category_group_enabled)); + // Implementation detail: internal macro to create static category and add // event if the category is enabled. #define INTERNAL_TRACE_EVENT_ADD(phase, category_group, name, flags, ...) \ do { \ INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group); \ - if (*INTERNAL_TRACE_EVENT_UID(catstatic)) { \ + if (*INTERNAL_TRACE_EVENT_UID(category_group_enabled)) { \ trace_event_internal::AddTraceEvent( \ - phase, INTERNAL_TRACE_EVENT_UID(catstatic), name, \ + phase, INTERNAL_TRACE_EVENT_UID(category_group_enabled), name, \ trace_event_internal::kNoEventId, flags, ##__VA_ARGS__); \ } \ } while (0) @@ -855,16 +882,15 @@ TRACE_EVENT_API_CLASS_EXPORT extern \ // ends. #define INTERNAL_TRACE_EVENT_ADD_SCOPED(category_group, name, ...) \ INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group); \ - trace_event_internal::TraceEndOnScopeClose \ - INTERNAL_TRACE_EVENT_UID(profileScope); \ - if (*INTERNAL_TRACE_EVENT_UID(catstatic)) { \ - trace_event_internal::AddTraceEvent( \ - TRACE_EVENT_PHASE_BEGIN, \ - INTERNAL_TRACE_EVENT_UID(catstatic), \ + trace_event_internal::ScopedTracer INTERNAL_TRACE_EVENT_UID(tracer); \ + if (*INTERNAL_TRACE_EVENT_UID(category_group_enabled)) { \ + base::debug::TraceEventHandle h = trace_event_internal::AddTraceEvent( \ + TRACE_EVENT_PHASE_COMPLETE, \ + INTERNAL_TRACE_EVENT_UID(category_group_enabled), \ name, trace_event_internal::kNoEventId, \ TRACE_EVENT_FLAG_NONE, ##__VA_ARGS__); \ - INTERNAL_TRACE_EVENT_UID(profileScope).Initialize( \ - INTERNAL_TRACE_EVENT_UID(catstatic), name); \ + INTERNAL_TRACE_EVENT_UID(tracer).Initialize( \ + INTERNAL_TRACE_EVENT_UID(category_group_enabled), name, h); \ } // Implementation detail: internal macro to create static category and add @@ -873,12 +899,12 @@ TRACE_EVENT_API_CLASS_EXPORT extern \ flags, ...) \ do { \ INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group); \ - if (*INTERNAL_TRACE_EVENT_UID(catstatic)) { \ + if (*INTERNAL_TRACE_EVENT_UID(category_group_enabled)) { \ unsigned char trace_event_flags = flags | TRACE_EVENT_FLAG_HAS_ID; \ trace_event_internal::TraceID trace_event_trace_id( \ id, &trace_event_flags); \ trace_event_internal::AddTraceEvent( \ - phase, INTERNAL_TRACE_EVENT_UID(catstatic), \ + phase, INTERNAL_TRACE_EVENT_UID(category_group_enabled), \ name, trace_event_trace_id.data(), trace_event_flags, \ ##__VA_ARGS__); \ } \ @@ -890,12 +916,12 @@ TRACE_EVENT_API_CLASS_EXPORT extern \ category_group, name, id, thread_id, timestamp, flags, ...) \ do { \ INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group); \ - if (*INTERNAL_TRACE_EVENT_UID(catstatic)) { \ + if (*INTERNAL_TRACE_EVENT_UID(category_group_enabled)) { \ unsigned char trace_event_flags = flags | TRACE_EVENT_FLAG_HAS_ID; \ trace_event_internal::TraceID trace_event_trace_id( \ id, &trace_event_flags); \ trace_event_internal::AddTraceEventWithThreadIdAndTimestamp( \ - phase, INTERNAL_TRACE_EVENT_UID(catstatic), \ + phase, INTERNAL_TRACE_EVENT_UID(category_group_enabled), \ name, trace_event_trace_id.data(), \ thread_id, base::TimeTicks::FromInternalValue(timestamp), \ trace_event_flags, ##__VA_ARGS__); \ @@ -910,9 +936,11 @@ TRACE_EVENT_API_CLASS_EXPORT extern \ // Phase indicates the nature of an event entry. E.g. part of a begin/end pair. #define TRACE_EVENT_PHASE_BEGIN ('B') #define TRACE_EVENT_PHASE_END ('E') +#define TRACE_EVENT_PHASE_COMPLETE ('X') #define TRACE_EVENT_PHASE_INSTANT ('i') #define TRACE_EVENT_PHASE_ASYNC_BEGIN ('S') -#define TRACE_EVENT_PHASE_ASYNC_STEP ('T') +#define TRACE_EVENT_PHASE_ASYNC_STEP_INTO ('T') +#define TRACE_EVENT_PHASE_ASYNC_STEP_PAST ('p') #define TRACE_EVENT_PHASE_ASYNC_END ('F') #define TRACE_EVENT_PHASE_FLOW_BEGIN ('s') #define TRACE_EVENT_PHASE_FLOW_STEP ('t') @@ -1133,7 +1161,8 @@ static inline void SetTraceValue(const std::string& arg, // pointers to the internal c_str and pass through to the tracing API, // the arg_values must live throughout these procedures. -static inline void AddTraceEventWithThreadIdAndTimestamp( +static inline base::debug::TraceEventHandle +AddTraceEventWithThreadIdAndTimestamp( char phase, const unsigned char* category_group_enabled, const char* name, @@ -1142,34 +1171,17 @@ static inline void AddTraceEventWithThreadIdAndTimestamp( const base::TimeTicks& timestamp, unsigned char flags, const char* arg1_name, - scoped_ptr<base::debug::ConvertableToTraceFormat> arg1_val) { + const scoped_refptr<base::debug::ConvertableToTraceFormat>& arg1_val) { const int num_args = 1; unsigned char arg_types[1] = { TRACE_VALUE_TYPE_CONVERTABLE }; - scoped_ptr<base::debug::ConvertableToTraceFormat> convertable_values[1]; - convertable_values[0].reset(arg1_val.release()); - - TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_THREAD_ID_AND_TIMESTAMP( + return TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_THREAD_ID_AND_TIMESTAMP( phase, category_group_enabled, name, id, thread_id, timestamp, - num_args, &arg1_name, arg_types, NULL, convertable_values, flags); -} - -static inline void AddTraceEvent( - char phase, - const unsigned char* category_group_enabled, - const char* name, - unsigned long long id, - unsigned char flags, - const char* arg1_name, - scoped_ptr<base::debug::ConvertableToTraceFormat> arg1_val) { - int thread_id = static_cast<int>(base::PlatformThread::CurrentId()); - base::TimeTicks now = base::TimeTicks::NowFromSystemTraceTime(); - AddTraceEventWithThreadIdAndTimestamp(phase, category_group_enabled, name, id, - thread_id, now, flags, arg1_name, - arg1_val.Pass()); + num_args, &arg1_name, arg_types, NULL, &arg1_val, flags); } template<class ARG1_TYPE> -static inline void AddTraceEventWithThreadIdAndTimestamp( +static inline base::debug::TraceEventHandle +AddTraceEventWithThreadIdAndTimestamp( char phase, const unsigned char* category_group_enabled, const char* name, @@ -1178,9 +1190,9 @@ static inline void AddTraceEventWithThreadIdAndTimestamp( const base::TimeTicks& timestamp, unsigned char flags, const char* arg1_name, - ARG1_TYPE arg1_val, + const ARG1_TYPE& arg1_val, const char* arg2_name, - scoped_ptr<base::debug::ConvertableToTraceFormat> arg2_val) { + const scoped_refptr<base::debug::ConvertableToTraceFormat>& arg2_val) { const int num_args = 2; const char* arg_names[2] = { arg1_name, arg2_name }; @@ -1189,35 +1201,17 @@ static inline void AddTraceEventWithThreadIdAndTimestamp( SetTraceValue(arg1_val, &arg_types[0], &arg_values[0]); arg_types[1] = TRACE_VALUE_TYPE_CONVERTABLE; - scoped_ptr<base::debug::ConvertableToTraceFormat> convertable_values[2]; - convertable_values[1].reset(arg2_val.release()); + scoped_refptr<base::debug::ConvertableToTraceFormat> convertable_values[2]; + convertable_values[1] = arg2_val; - TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_THREAD_ID_AND_TIMESTAMP( + return TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_THREAD_ID_AND_TIMESTAMP( phase, category_group_enabled, name, id, thread_id, timestamp, num_args, arg_names, arg_types, arg_values, convertable_values, flags); } -template<class ARG1_TYPE> -static inline void AddTraceEvent( - char phase, - const unsigned char* category_group_enabled, - const char* name, - unsigned long long id, - unsigned char flags, - const char* arg1_name, - ARG1_TYPE arg1_val, - const char* arg2_name, - scoped_ptr<base::debug::ConvertableToTraceFormat> arg2_val) { - int thread_id = static_cast<int>(base::PlatformThread::CurrentId()); - base::TimeTicks now = base::TimeTicks::NowFromSystemTraceTime(); - AddTraceEventWithThreadIdAndTimestamp(phase, category_group_enabled, name, id, - thread_id, now, flags, - arg1_name, arg1_val, - arg2_name, arg2_val.Pass()); -} - template<class ARG2_TYPE> -static inline void AddTraceEventWithThreadIdAndTimestamp( +static inline base::debug::TraceEventHandle +AddTraceEventWithThreadIdAndTimestamp( char phase, const unsigned char* category_group_enabled, const char* name, @@ -1226,9 +1220,9 @@ static inline void AddTraceEventWithThreadIdAndTimestamp( const base::TimeTicks& timestamp, unsigned char flags, const char* arg1_name, - scoped_ptr<base::debug::ConvertableToTraceFormat> arg1_val, + const scoped_refptr<base::debug::ConvertableToTraceFormat>& arg1_val, const char* arg2_name, - ARG2_TYPE arg2_val) { + const ARG2_TYPE& arg2_val) { const int num_args = 2; const char* arg_names[2] = { arg1_name, arg2_name }; @@ -1238,34 +1232,16 @@ static inline void AddTraceEventWithThreadIdAndTimestamp( arg_values[0] = 0; SetTraceValue(arg2_val, &arg_types[1], &arg_values[1]); - scoped_ptr<base::debug::ConvertableToTraceFormat> convertable_values[2]; - convertable_values[0].reset(arg1_val.release()); + scoped_refptr<base::debug::ConvertableToTraceFormat> convertable_values[2]; + convertable_values[0] = arg1_val; - TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_THREAD_ID_AND_TIMESTAMP( + return TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_THREAD_ID_AND_TIMESTAMP( phase, category_group_enabled, name, id, thread_id, timestamp, num_args, arg_names, arg_types, arg_values, convertable_values, flags); } -template<class ARG2_TYPE> -static inline void AddTraceEvent( - char phase, - const unsigned char* category_group_enabled, - const char* name, - unsigned long long id, - unsigned char flags, - const char* arg1_name, - scoped_ptr<base::debug::ConvertableToTraceFormat> arg1_val, - const char* arg2_name, - ARG2_TYPE arg2_val) { - int thread_id = static_cast<int>(base::PlatformThread::CurrentId()); - base::TimeTicks now = base::TimeTicks::NowFromSystemTraceTime(); - AddTraceEventWithThreadIdAndTimestamp(phase, category_group_enabled, name, id, - thread_id, now, flags, - arg1_name, arg1_val.Pass(), - arg2_name, arg2_val); -} - -static inline void AddTraceEventWithThreadIdAndTimestamp( +static inline base::debug::TraceEventHandle +AddTraceEventWithThreadIdAndTimestamp( char phase, const unsigned char* category_group_enabled, const char* name, @@ -1274,41 +1250,23 @@ static inline void AddTraceEventWithThreadIdAndTimestamp( const base::TimeTicks& timestamp, unsigned char flags, const char* arg1_name, - scoped_ptr<base::debug::ConvertableToTraceFormat> arg1_val, + const scoped_refptr<base::debug::ConvertableToTraceFormat>& arg1_val, const char* arg2_name, - scoped_ptr<base::debug::ConvertableToTraceFormat> arg2_val) { + const scoped_refptr<base::debug::ConvertableToTraceFormat>& arg2_val) { const int num_args = 2; const char* arg_names[2] = { arg1_name, arg2_name }; unsigned char arg_types[2] = { TRACE_VALUE_TYPE_CONVERTABLE, TRACE_VALUE_TYPE_CONVERTABLE }; - scoped_ptr<base::debug::ConvertableToTraceFormat> convertable_values[2]; - convertable_values[0].reset(arg1_val.release()); - convertable_values[1].reset(arg2_val.release()); + scoped_refptr<base::debug::ConvertableToTraceFormat> convertable_values[2] = + { arg1_val, arg2_val }; - TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_THREAD_ID_AND_TIMESTAMP( + return TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_THREAD_ID_AND_TIMESTAMP( phase, category_group_enabled, name, id, thread_id, timestamp, num_args, arg_names, arg_types, NULL, convertable_values, flags); } -static inline void AddTraceEvent( - char phase, - const unsigned char* category_group_enabled, - const char* name, - unsigned long long id, - unsigned char flags, - const char* arg1_name, - scoped_ptr<base::debug::ConvertableToTraceFormat> arg1_val, - const char* arg2_name, - scoped_ptr<base::debug::ConvertableToTraceFormat> arg2_val) { - int thread_id = static_cast<int>(base::PlatformThread::CurrentId()); - base::TimeTicks now = base::TimeTicks::NowFromSystemTraceTime(); - AddTraceEventWithThreadIdAndTimestamp(phase, category_group_enabled, name, id, - thread_id, now, flags, - arg1_name, arg1_val.Pass(), - arg2_name, arg2_val.Pass()); -} - -static inline void AddTraceEventWithThreadIdAndTimestamp( +static inline base::debug::TraceEventHandle +AddTraceEventWithThreadIdAndTimestamp( char phase, const unsigned char* category_group_enabled, const char* name, @@ -1316,24 +1274,26 @@ static inline void AddTraceEventWithThreadIdAndTimestamp( int thread_id, const base::TimeTicks& timestamp, unsigned char flags) { - TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_THREAD_ID_AND_TIMESTAMP( + return TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_THREAD_ID_AND_TIMESTAMP( phase, category_group_enabled, name, id, thread_id, timestamp, kZeroNumArgs, NULL, NULL, NULL, NULL, flags); } -static inline void AddTraceEvent(char phase, - const unsigned char* category_group_enabled, - const char* name, - unsigned long long id, - unsigned char flags) { +static inline base::debug::TraceEventHandle AddTraceEvent( + char phase, + const unsigned char* category_group_enabled, + const char* name, + unsigned long long id, + unsigned char flags) { int thread_id = static_cast<int>(base::PlatformThread::CurrentId()); base::TimeTicks now = base::TimeTicks::NowFromSystemTraceTime(); - AddTraceEventWithThreadIdAndTimestamp(phase, category_group_enabled, name, id, - thread_id, now, flags); + return AddTraceEventWithThreadIdAndTimestamp(phase, category_group_enabled, + name, id, thread_id, now, flags); } template<class ARG1_TYPE> -static inline void AddTraceEventWithThreadIdAndTimestamp( +static inline base::debug::TraceEventHandle +AddTraceEventWithThreadIdAndTimestamp( char phase, const unsigned char* category_group_enabled, const char* name, @@ -1347,28 +1307,30 @@ static inline void AddTraceEventWithThreadIdAndTimestamp( unsigned char arg_types[1]; unsigned long long arg_values[1]; SetTraceValue(arg1_val, &arg_types[0], &arg_values[0]); - TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_THREAD_ID_AND_TIMESTAMP( + return TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_THREAD_ID_AND_TIMESTAMP( phase, category_group_enabled, name, id, thread_id, timestamp, num_args, &arg1_name, arg_types, arg_values, NULL, flags); } template<class ARG1_TYPE> -static inline void AddTraceEvent(char phase, - const unsigned char* category_group_enabled, - const char* name, - unsigned long long id, - unsigned char flags, - const char* arg1_name, - const ARG1_TYPE& arg1_val) { +static inline base::debug::TraceEventHandle AddTraceEvent( + char phase, + const unsigned char* category_group_enabled, + const char* name, + unsigned long long id, + unsigned char flags, + const char* arg1_name, + const ARG1_TYPE& arg1_val) { int thread_id = static_cast<int>(base::PlatformThread::CurrentId()); base::TimeTicks now = base::TimeTicks::NowFromSystemTraceTime(); - AddTraceEventWithThreadIdAndTimestamp(phase, category_group_enabled, name, id, - thread_id, now, flags, arg1_name, - arg1_val); + return AddTraceEventWithThreadIdAndTimestamp(phase, category_group_enabled, + name, id, thread_id, now, flags, + arg1_name, arg1_val); } template<class ARG1_TYPE, class ARG2_TYPE> -static inline void AddTraceEventWithThreadIdAndTimestamp( +static inline base::debug::TraceEventHandle +AddTraceEventWithThreadIdAndTimestamp( char phase, const unsigned char* category_group_enabled, const char* name, @@ -1386,64 +1348,52 @@ static inline void AddTraceEventWithThreadIdAndTimestamp( unsigned long long arg_values[2]; SetTraceValue(arg1_val, &arg_types[0], &arg_values[0]); SetTraceValue(arg2_val, &arg_types[1], &arg_values[1]); - TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_THREAD_ID_AND_TIMESTAMP( + return TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_THREAD_ID_AND_TIMESTAMP( phase, category_group_enabled, name, id, thread_id, timestamp, num_args, arg_names, arg_types, arg_values, NULL, flags); } template<class ARG1_TYPE, class ARG2_TYPE> -static inline void AddTraceEvent(char phase, - const unsigned char* category_group_enabled, - const char* name, - unsigned long long id, - unsigned char flags, - const char* arg1_name, - const ARG1_TYPE& arg1_val, - const char* arg2_name, - const ARG2_TYPE& arg2_val) { +static inline base::debug::TraceEventHandle AddTraceEvent( + char phase, + const unsigned char* category_group_enabled, + const char* name, + unsigned long long id, + unsigned char flags, + const char* arg1_name, + const ARG1_TYPE& arg1_val, + const char* arg2_name, + const ARG2_TYPE& arg2_val) { int thread_id = static_cast<int>(base::PlatformThread::CurrentId()); base::TimeTicks now = base::TimeTicks::NowFromSystemTraceTime(); - AddTraceEventWithThreadIdAndTimestamp(phase, category_group_enabled, name, id, - thread_id, now, flags, arg1_name, - arg1_val, arg2_name, arg2_val); + return AddTraceEventWithThreadIdAndTimestamp(phase, category_group_enabled, + name, id, thread_id, now, flags, + arg1_name, arg1_val, + arg2_name, arg2_val); } -// Used by TRACE_EVENTx macro. Do not use directly. -class TRACE_EVENT_API_CLASS_EXPORT TraceEndOnScopeClose { +// Used by TRACE_EVENTx macros. Do not use directly. +class TRACE_EVENT_API_CLASS_EXPORT ScopedTracer { public: // Note: members of data_ intentionally left uninitialized. See Initialize. - TraceEndOnScopeClose() : p_data_(NULL) {} - ~TraceEndOnScopeClose() { - if (p_data_) - AddEventIfEnabled(); + ScopedTracer() : p_data_(NULL) {} + + ~ScopedTracer() { + if (p_data_ && *data_.category_group_enabled) + TRACE_EVENT_API_UPDATE_TRACE_EVENT_DURATION( + data_.category_group_enabled, data_.name, data_.event_handle); } void Initialize(const unsigned char* category_group_enabled, - const char* name) { + const char* name, + base::debug::TraceEventHandle event_handle) { data_.category_group_enabled = category_group_enabled; data_.name = name; + data_.event_handle = event_handle; p_data_ = &data_; } private: - // Add the end event if the category is still enabled. - void AddEventIfEnabled() { - // Only called when p_data_ is non-null. - if (*p_data_->category_group_enabled) { - TRACE_EVENT_API_ADD_TRACE_EVENT( - TRACE_EVENT_PHASE_END, // phase - p_data_->category_group_enabled, // category enabled - p_data_->name, // name - kNoEventId, // id - kZeroNumArgs, // num_args - NULL, // arg_names - NULL, // arg_types - NULL, // arg_values - NULL, // convertable_values - TRACE_EVENT_FLAG_NONE); // flags - } - } - // This Data struct workaround is to avoid initializing all the members // in Data during construction of this object, since this object is always // constructed, even when tracing is disabled. If the members of Data were @@ -1452,36 +1402,32 @@ class TRACE_EVENT_API_CLASS_EXPORT TraceEndOnScopeClose { struct Data { const unsigned char* category_group_enabled; const char* name; + base::debug::TraceEventHandle event_handle; }; Data* p_data_; Data data_; }; // Used by TRACE_EVENT_BINARY_EFFICIENTx macro. Do not use directly. -class TRACE_EVENT_API_CLASS_EXPORT ScopedTrace { +class TRACE_EVENT_API_CLASS_EXPORT ScopedTraceBinaryEfficient { public: - ScopedTrace(TRACE_EVENT_API_ATOMIC_WORD* event_uid, const char* name); - ~ScopedTrace(); + ScopedTraceBinaryEfficient(const char* category_group, const char* name); + ~ScopedTraceBinaryEfficient(); private: const unsigned char* category_group_enabled_; const char* name_; + base::debug::TraceEventHandle event_handle_; }; -// A support macro for TRACE_EVENT_BINARY_EFFICIENTx -#define INTERNAL_TRACE_EVENT_BINARY_EFFICIENT_ADD_SCOPED( \ - category_group, name, ...) \ - static TRACE_EVENT_API_ATOMIC_WORD INTERNAL_TRACE_EVENT_UID(atomic) = 0; \ - trace_event_internal::ScopedTrace \ - INTERNAL_TRACE_EVENT_UID(profileScope)( \ - &INTERNAL_TRACE_EVENT_UID(atomic), name); \ - // This macro generates less code then TRACE_EVENT0 but is also // slower to execute when tracing is off. It should generally only be // used with code that is seldom executed or conditionally executed // when debugging. +// For now the category_group must be "gpu". #define TRACE_EVENT_BINARY_EFFICIENT0(category_group, name) \ - INTERNAL_TRACE_EVENT_BINARY_EFFICIENT_ADD_SCOPED(category_group, name) + trace_event_internal::ScopedTraceBinaryEfficient \ + INTERNAL_TRACE_EVENT_UID(scoped_trace)(category_group, name); // TraceEventSamplingStateScope records the current sampling state // and sets a new sampling state. When the scope exists, it restores diff --git a/chromium/base/debug/trace_event_android.cc b/chromium/base/debug/trace_event_android.cc index 78d9de66747..567c48eb293 100644 --- a/chromium/base/debug/trace_event_android.cc +++ b/chromium/base/debug/trace_event_android.cc @@ -10,6 +10,7 @@ #include "base/format_macros.h" #include "base/logging.h" #include "base/strings/stringprintf.h" +#include "base/synchronization/waitable_event.h" namespace { @@ -21,18 +22,18 @@ void WriteEvent( const char* category_group, const char* name, unsigned long long id, - int num_args, const char** arg_names, const unsigned char* arg_types, - const unsigned long long* arg_values, - scoped_ptr<base::debug::ConvertableToTraceFormat> convertable_values[], + const base::debug::TraceEvent::TraceValue* arg_values, + const scoped_refptr<base::debug::ConvertableToTraceFormat>* + convertable_values, unsigned char flags) { std::string out = base::StringPrintf("%c|%d|%s", phase, getpid(), name); if (flags & TRACE_EVENT_FLAG_HAS_ID) base::StringAppendF(&out, "-%" PRIx64, static_cast<uint64>(id)); out += '|'; - for (int i = 0; i < num_args; ++i) { + for (int i = 0; i < base::debug::kTraceMaxNumArgs && arg_names[i]; ++i) { if (i) out += ';'; out += arg_names[i]; @@ -41,9 +42,8 @@ void WriteEvent( if (arg_types[i] == TRACE_VALUE_TYPE_CONVERTABLE) { convertable_values[i]->AppendAsTraceFormat(&out); } else { - base::debug::TraceEvent::TraceValue value; - value.as_uint = arg_values[i]; - base::debug::TraceEvent::AppendValueAsJSON(arg_types[i], value, &out); + base::debug::TraceEvent::AppendValueAsJSON( + arg_types[i], arg_values[i], &out); } // Remove the quotes which may confuse the atrace script. ReplaceSubstringsAfterOffset(&out, value_start, "\\\"", "'"); @@ -58,78 +58,112 @@ void WriteEvent( write(g_atrace_fd, out.c_str(), out.size()); } +void NoOpOutputCallback(base::WaitableEvent* complete_event, + const scoped_refptr<base::RefCountedString>&, + bool has_more_events) { + if (!has_more_events) + complete_event->Signal(); +} + +void EndChromeTracing(base::debug::TraceLog* trace_log, + base::WaitableEvent* complete_event) { + trace_log->SetDisabled(); + // Delete the buffered trace events as they have been sent to atrace. + trace_log->Flush(base::Bind(&NoOpOutputCallback, complete_event)); +} + } // namespace namespace base { namespace debug { +// These functions support Android systrace.py when 'webview' category is +// traced. With the new adb_profile_chrome, we may have two phases: +// - before WebView is ready for combined tracing, we can use adb_profile_chrome +// to trace android categories other than 'webview' and chromium categories. +// In this way we can avoid the conflict between StartATrace/StopATrace and +// the intents. +// - TODO(wangxianzhu): after WebView is ready for combined tracing, remove +// StartATrace, StopATrace and SendToATrace, and perhaps send Java traces +// directly to atrace in trace_event_binding.cc. + void TraceLog::StartATrace() { - AutoLock lock(lock_); + if (g_atrace_fd != -1) + return; + + g_atrace_fd = open(kATraceMarkerFile, O_WRONLY); if (g_atrace_fd == -1) { - g_atrace_fd = open(kATraceMarkerFile, O_WRONLY); - if (g_atrace_fd == -1) { - LOG(WARNING) << "Couldn't open " << kATraceMarkerFile; - } else { - UpdateCategoryGroupEnabledFlags(); - } + PLOG(WARNING) << "Couldn't open " << kATraceMarkerFile; + return; } + SetEnabled(CategoryFilter(CategoryFilter::kDefaultCategoryFilterString), + RECORD_CONTINUOUSLY); } void TraceLog::StopATrace() { - AutoLock lock(lock_); - if (g_atrace_fd != -1) { - close(g_atrace_fd); - g_atrace_fd = -1; - UpdateCategoryGroupEnabledFlags(); - } + if (g_atrace_fd == -1) + return; + + close(g_atrace_fd); + g_atrace_fd = -1; + + // TraceLog::Flush() requires the current thread to have a message loop, but + // this thread called from Java may not have one, so flush in another thread. + Thread end_chrome_tracing_thread("end_chrome_tracing"); + WaitableEvent complete_event(false, false); + end_chrome_tracing_thread.Start(); + end_chrome_tracing_thread.message_loop()->PostTask( + FROM_HERE, base::Bind(&EndChromeTracing, Unretained(this), + Unretained(&complete_event))); + complete_event.Wait(); } -void TraceLog::SendToATrace( - char phase, - const char* category_group, - const char* name, - unsigned long long id, - int num_args, - const char** arg_names, - const unsigned char* arg_types, - const unsigned long long* arg_values, - scoped_ptr<ConvertableToTraceFormat> convertable_values[], - unsigned char flags) { +void TraceEvent::SendToATrace() { if (g_atrace_fd == -1) return; - switch (phase) { + const char* category_group = + TraceLog::GetCategoryGroupName(category_group_enabled_); + + switch (phase_) { case TRACE_EVENT_PHASE_BEGIN: - WriteEvent('B', category_group, name, id, - num_args, arg_names, arg_types, arg_values, convertable_values, - flags); + WriteEvent('B', category_group, name_, id_, + arg_names_, arg_types_, arg_values_, convertable_values_, + flags_); + break; + + case TRACE_EVENT_PHASE_COMPLETE: + WriteEvent(duration_.ToInternalValue() == -1 ? 'B' : 'E', + category_group, name_, id_, + arg_names_, arg_types_, arg_values_, convertable_values_, + flags_); break; case TRACE_EVENT_PHASE_END: // Though a single 'E' is enough, here append pid, name and // category_group etc. So that unpaired events can be found easily. - WriteEvent('E', category_group, name, id, - num_args, arg_names, arg_types, arg_values, convertable_values, - flags); + WriteEvent('E', category_group, name_, id_, + arg_names_, arg_types_, arg_values_, convertable_values_, + flags_); break; case TRACE_EVENT_PHASE_INSTANT: // Simulate an instance event with a pair of begin/end events. - WriteEvent('B', category_group, name, id, - num_args, arg_names, arg_types, arg_values, convertable_values, - flags); + WriteEvent('B', category_group, name_, id_, + arg_names_, arg_types_, arg_values_, convertable_values_, + flags_); write(g_atrace_fd, "E", 1); break; case TRACE_EVENT_PHASE_COUNTER: - for (int i = 0; i < num_args; ++i) { - DCHECK(arg_types[i] == TRACE_VALUE_TYPE_INT); - std::string out = base::StringPrintf("C|%d|%s-%s", - getpid(), name, arg_names[i]); - if (flags & TRACE_EVENT_FLAG_HAS_ID) - StringAppendF(&out, "-%" PRIx64, static_cast<uint64>(id)); + for (int i = 0; i < kTraceMaxNumArgs && arg_names_[i]; ++i) { + DCHECK(arg_types_[i] == TRACE_VALUE_TYPE_INT); + std::string out = base::StringPrintf( + "C|%d|%s-%s", getpid(), name_, arg_names_[i]); + if (flags_ & TRACE_EVENT_FLAG_HAS_ID) + StringAppendF(&out, "-%" PRIx64, static_cast<uint64>(id_)); StringAppendF(&out, "|%d|%s", - static_cast<int>(arg_values[i]), category_group); + static_cast<int>(arg_values_[i].as_int), category_group); write(g_atrace_fd, out.c_str(), out.size()); } break; @@ -140,18 +174,24 @@ void TraceLog::SendToATrace( } } -// Must be called with lock_ locked. -void TraceLog::ApplyATraceEnabledFlag(unsigned char* category_group_enabled) { - if (g_atrace_fd == -1) - return; - - // Don't enable disabled-by-default categories for atrace. - const char* category_group = GetCategoryGroupName(category_group_enabled); - if (strncmp(category_group, TRACE_DISABLED_BY_DEFAULT(""), - strlen(TRACE_DISABLED_BY_DEFAULT(""))) == 0) +void TraceLog::AddClockSyncMetadataEvent() { + int atrace_fd = open(kATraceMarkerFile, O_WRONLY | O_APPEND); + if (atrace_fd == -1) { + PLOG(WARNING) << "Couldn't open " << kATraceMarkerFile; return; + } - *category_group_enabled |= ATRACE_ENABLED; + // Android's kernel trace system has a trace_marker feature: this is a file on + // debugfs that takes the written data and pushes it onto the trace + // buffer. So, to establish clock sync, we write our monotonic clock into that + // trace buffer. + TimeTicks now = TimeTicks::NowFromSystemTraceTime(); + double now_in_seconds = now.ToInternalValue() / 1000000.0; + std::string marker = StringPrintf( + "trace_event_clock_sync: parent_ts=%f\n", now_in_seconds); + if (write(atrace_fd, marker.c_str(), marker.size()) == -1) + PLOG(WARNING) << "Couldn't write to " << kATraceMarkerFile; + close(atrace_fd); } } // namespace debug diff --git a/chromium/base/debug/trace_event_impl.cc b/chromium/base/debug/trace_event_impl.cc index fd683501efd..e774f621e04 100644 --- a/chromium/base/debug/trace_event_impl.cc +++ b/chromium/base/debug/trace_event_impl.cc @@ -12,11 +12,13 @@ #include "base/debug/leak_annotations.h" #include "base/debug/trace_event.h" #include "base/format_macros.h" +#include "base/json/string_escape.h" #include "base/lazy_instance.h" #include "base/memory/singleton.h" #include "base/message_loop/message_loop.h" #include "base/process/process_metrics.h" #include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" #include "base/strings/string_split.h" #include "base/strings/string_tokenizer.h" #include "base/strings/string_util.h" @@ -56,13 +58,19 @@ const int kOverheadReportThresholdInMicroseconds = 50; // Controls the number of trace events we will buffer in-memory // before throwing them away. -const size_t kTraceEventVectorBufferSize = 250000; -const size_t kTraceEventRingBufferSize = kTraceEventVectorBufferSize / 4; -const size_t kTraceEventThreadLocalBufferSize = 64; -const size_t kTraceEventBatchSize = 1000; -const size_t kTraceEventInitialBufferSize = 1024; +const size_t kTraceBufferChunkSize = TraceBufferChunk::kTraceBufferChunkSize; +const size_t kTraceEventVectorBufferChunks = 256000 / kTraceBufferChunkSize; +const size_t kTraceEventRingBufferChunks = kTraceEventVectorBufferChunks / 4; +const size_t kTraceEventBatchChunks = 1000 / kTraceBufferChunkSize; +// Can store results for 30 seconds with 1 ms sampling interval. +const size_t kMonitorTraceEventBufferChunks = 30000 / kTraceBufferChunkSize; +// ECHO_TO_CONSOLE needs a small buffer to hold the unfinished COMPLETE events. +const size_t kEchoToConsoleTraceEventBufferChunks = 256; -const int kThreadFlushTimeoutMs = 1000; +const int kThreadFlushTimeoutMs = 3000; + +// These categories will cause deadlock when ECHO_TO_CONSOLE. crbug.com/325575. +const char kEchoToConsoleCategoryFilter[] = "-ipc,-task"; #define MAX_CATEGORY_GROUPS 100 @@ -94,10 +102,6 @@ int g_category_index = g_num_builtin_categories; // Skip default categories. LazyInstance<ThreadLocalPointer<const char> >::Leaky g_current_thread_name = LAZY_INSTANCE_INITIALIZER; -const char kRecordUntilFull[] = "record-until-full"; -const char kRecordContinuously[] = "record-continuously"; -const char kEnableSampling[] = "enable-sampling"; - TimeTicks ThreadNow() { return TimeTicks::IsThreadNowSupported() ? TimeTicks::ThreadNow() : TimeTicks(); @@ -105,186 +109,347 @@ TimeTicks ThreadNow() { class TraceBufferRingBuffer : public TraceBuffer { public: - TraceBufferRingBuffer() - : unused_event_index_(0), - oldest_event_index_(0) { - logged_events_.reserve(kTraceEventInitialBufferSize); + TraceBufferRingBuffer(size_t max_chunks) + : max_chunks_(max_chunks), + recyclable_chunks_queue_(new size_t[queue_capacity()]), + queue_head_(0), + queue_tail_(max_chunks), + current_iteration_index_(0), + current_chunk_seq_(1) { + chunks_.reserve(max_chunks); + for (size_t i = 0; i < max_chunks; ++i) + recyclable_chunks_queue_[i] = i; + } + + virtual scoped_ptr<TraceBufferChunk> GetChunk(size_t* index) OVERRIDE { + // Because the number of threads is much less than the number of chunks, + // the queue should never be empty. + DCHECK(!QueueIsEmpty()); + + *index = recyclable_chunks_queue_[queue_head_]; + queue_head_ = NextQueueIndex(queue_head_); + current_iteration_index_ = queue_head_; + + if (*index >= chunks_.size()) + chunks_.resize(*index + 1); + + TraceBufferChunk* chunk = chunks_[*index]; + chunks_[*index] = NULL; // Put NULL in the slot of a in-flight chunk. + if (chunk) + chunk->Reset(current_chunk_seq_++); + else + chunk = new TraceBufferChunk(current_chunk_seq_++); + + return scoped_ptr<TraceBufferChunk>(chunk); } - virtual ~TraceBufferRingBuffer() {} + virtual void ReturnChunk(size_t index, + scoped_ptr<TraceBufferChunk> chunk) OVERRIDE { + // When this method is called, the queue should not be full because it + // can contain all chunks including the one to be returned. + DCHECK(!QueueIsFull()); + DCHECK(chunk); + DCHECK_LT(index, chunks_.size()); + DCHECK(!chunks_[index]); + chunks_[index] = chunk.release(); + recyclable_chunks_queue_[queue_tail_] = index; + queue_tail_ = NextQueueIndex(queue_tail_); + } - virtual void AddEvent(const TraceEvent& event) OVERRIDE { - if (unused_event_index_ < Size()) - logged_events_[unused_event_index_] = event; - else - logged_events_.push_back(event); + virtual bool IsFull() const OVERRIDE { + return false; + } - unused_event_index_ = NextIndex(unused_event_index_); - if (unused_event_index_ == oldest_event_index_) { - oldest_event_index_ = NextIndex(oldest_event_index_); - } + virtual size_t Size() const OVERRIDE { + // This is approximate because not all of the chunks are full. + return chunks_.size() * kTraceBufferChunkSize; } - virtual bool HasMoreEvents() const OVERRIDE { - return oldest_event_index_ != unused_event_index_; + virtual size_t Capacity() const OVERRIDE { + return max_chunks_ * kTraceBufferChunkSize; } - virtual const TraceEvent& NextEvent() OVERRIDE { - DCHECK(HasMoreEvents()); + virtual TraceEvent* GetEventByHandle(TraceEventHandle handle) OVERRIDE { + if (handle.chunk_index >= chunks_.size()) + return NULL; + TraceBufferChunk* chunk = chunks_[handle.chunk_index]; + if (!chunk || chunk->seq() != handle.chunk_seq) + return NULL; + return chunk->GetEventAt(handle.event_index); + } - size_t next = oldest_event_index_; - oldest_event_index_ = NextIndex(oldest_event_index_); - return GetEventAt(next); + virtual const TraceBufferChunk* NextChunk() OVERRIDE { + if (chunks_.empty()) + return NULL; + + while (current_iteration_index_ != queue_tail_) { + size_t chunk_index = recyclable_chunks_queue_[current_iteration_index_]; + current_iteration_index_ = NextQueueIndex(current_iteration_index_); + if (chunk_index >= chunks_.size()) // Skip uninitialized chunks. + continue; + DCHECK(chunks_[chunk_index]); + return chunks_[chunk_index]; + } + return NULL; } - virtual bool IsFull() const OVERRIDE { - return false; + virtual scoped_ptr<TraceBuffer> CloneForIteration() const OVERRIDE { + scoped_ptr<ClonedTraceBuffer> cloned_buffer(new ClonedTraceBuffer()); + for (size_t queue_index = queue_head_; queue_index != queue_tail_; + queue_index = NextQueueIndex(queue_index)) { + size_t chunk_index = recyclable_chunks_queue_[queue_index]; + if (chunk_index >= chunks_.size()) // Skip uninitialized chunks. + continue; + TraceBufferChunk* chunk = chunks_[chunk_index]; + cloned_buffer->chunks_.push_back(chunk ? chunk->Clone().release() : NULL); + } + return cloned_buffer.PassAs<TraceBuffer>(); } - virtual size_t CountEnabledByName( - const unsigned char* category, - const std::string& event_name) const OVERRIDE { - size_t notify_count = 0; - size_t index = oldest_event_index_; - while (index != unused_event_index_) { - const TraceEvent& event = GetEventAt(index); - if (category == event.category_group_enabled() && - strcmp(event_name.c_str(), event.name()) == 0) { - ++notify_count; - } - index = NextIndex(index); + private: + class ClonedTraceBuffer : public TraceBuffer { + public: + ClonedTraceBuffer() : current_iteration_index_(0) {} + + // The only implemented method. + virtual const TraceBufferChunk* NextChunk() OVERRIDE { + return current_iteration_index_ < chunks_.size() ? + chunks_[current_iteration_index_++] : NULL; + } + + virtual scoped_ptr<TraceBufferChunk> GetChunk(size_t* index) OVERRIDE { + NOTIMPLEMENTED(); + return scoped_ptr<TraceBufferChunk>(); + } + virtual void ReturnChunk(size_t index, + scoped_ptr<TraceBufferChunk>) OVERRIDE { + NOTIMPLEMENTED(); } - return notify_count; + virtual bool IsFull() const OVERRIDE { return false; } + virtual size_t Size() const OVERRIDE { return 0; } + virtual size_t Capacity() const OVERRIDE { return 0; } + virtual TraceEvent* GetEventByHandle(TraceEventHandle handle) OVERRIDE { + return NULL; + } + virtual scoped_ptr<TraceBuffer> CloneForIteration() const OVERRIDE { + NOTIMPLEMENTED(); + return scoped_ptr<TraceBuffer>(); + } + + size_t current_iteration_index_; + ScopedVector<TraceBufferChunk> chunks_; + }; + + bool QueueIsEmpty() const { + return queue_head_ == queue_tail_; } - virtual const TraceEvent& GetEventAt(size_t index) const OVERRIDE { - DCHECK(index < logged_events_.size()); - return logged_events_[index]; + size_t QueueSize() const { + return queue_tail_ > queue_head_ ? queue_tail_ - queue_head_ : + queue_tail_ + queue_capacity() - queue_head_; } - virtual size_t Size() const OVERRIDE { - return logged_events_.size(); + bool QueueIsFull() const { + return QueueSize() == queue_capacity() - 1; } - virtual size_t Capacity() const OVERRIDE { - return kTraceEventRingBufferSize; + size_t queue_capacity() const { + // One extra space to help distinguish full state and empty state. + return max_chunks_ + 1; } - private: - static size_t NextIndex(size_t index) { + size_t NextQueueIndex(size_t index) const { index++; - if (index >= kTraceEventRingBufferSize) + if (index >= queue_capacity()) index = 0; return index; } - size_t unused_event_index_; - size_t oldest_event_index_; - std::vector<TraceEvent> logged_events_; + size_t max_chunks_; + ScopedVector<TraceBufferChunk> chunks_; + + scoped_ptr<size_t[]> recyclable_chunks_queue_; + size_t queue_head_; + size_t queue_tail_; + + size_t current_iteration_index_; + uint32 current_chunk_seq_; DISALLOW_COPY_AND_ASSIGN(TraceBufferRingBuffer); }; class TraceBufferVector : public TraceBuffer { public: - TraceBufferVector() : current_iteration_index_(0) { - logged_events_.reserve(kTraceEventInitialBufferSize); + TraceBufferVector() + : in_flight_chunk_count_(0), + current_iteration_index_(0) { + chunks_.reserve(kTraceEventVectorBufferChunks); + } + + virtual scoped_ptr<TraceBufferChunk> GetChunk(size_t* index) OVERRIDE { + // This function may be called when adding normal events or indirectly from + // AddMetadataEventsWhileLocked(). We can not DECHECK(!IsFull()) because we + // have to add the metadata events and flush thread-local buffers even if + // the buffer is full. + *index = chunks_.size(); + chunks_.push_back(NULL); // Put NULL in the slot of a in-flight chunk. + ++in_flight_chunk_count_; + // + 1 because zero chunk_seq is not allowed. + return scoped_ptr<TraceBufferChunk>( + new TraceBufferChunk(static_cast<uint32>(*index) + 1)); + } + + virtual void ReturnChunk(size_t index, + scoped_ptr<TraceBufferChunk> chunk) OVERRIDE { + DCHECK_GT(in_flight_chunk_count_, 0u); + DCHECK_LT(index, chunks_.size()); + DCHECK(!chunks_[index]); + --in_flight_chunk_count_; + chunks_[index] = chunk.release(); } - virtual ~TraceBufferVector() { + virtual bool IsFull() const OVERRIDE { + return chunks_.size() >= kTraceEventVectorBufferChunks; } - virtual void AddEvent(const TraceEvent& event) OVERRIDE { - // Note, we have two callers which need to be handled: - // - AddEventToMainBufferWhileLocked() which has two cases: - // - called directly from AddTraceEventWithThreadIdAndTimeStamp() - // which checks if buffer is full and does an early exit if full; - // - called from ThreadLocalEventBuffer::FlushWhileLocked(); - // - AddThreadNameMetadataEvents(). - // We can not DECHECK(!IsFull()) because we have to add the metadata - // events and flush thread-local buffers even if the buffer is full. - logged_events_.push_back(event); + virtual size_t Size() const OVERRIDE { + // This is approximate because not all of the chunks are full. + return chunks_.size() * kTraceBufferChunkSize; } - virtual bool HasMoreEvents() const OVERRIDE { - return current_iteration_index_ < Size(); + virtual size_t Capacity() const OVERRIDE { + return kTraceEventVectorBufferChunks * kTraceBufferChunkSize; } - virtual const TraceEvent& NextEvent() OVERRIDE { - DCHECK(HasMoreEvents()); - return GetEventAt(current_iteration_index_++); + virtual TraceEvent* GetEventByHandle(TraceEventHandle handle) OVERRIDE { + if (handle.chunk_index >= chunks_.size()) + return NULL; + TraceBufferChunk* chunk = chunks_[handle.chunk_index]; + if (!chunk || chunk->seq() != handle.chunk_seq) + return NULL; + return chunk->GetEventAt(handle.event_index); } - virtual bool IsFull() const OVERRIDE { - return Size() >= kTraceEventVectorBufferSize; - } - - virtual size_t CountEnabledByName( - const unsigned char* category, - const std::string& event_name) const OVERRIDE { - size_t notify_count = 0; - for (size_t i = 0; i < Size(); i++) { - const TraceEvent& event = GetEventAt(i); - if (category == event.category_group_enabled() && - strcmp(event_name.c_str(), event.name()) == 0) { - ++notify_count; - } + virtual const TraceBufferChunk* NextChunk() OVERRIDE { + while (current_iteration_index_ < chunks_.size()) { + // Skip in-flight chunks. + const TraceBufferChunk* chunk = chunks_[current_iteration_index_++]; + if (chunk) + return chunk; } - return notify_count; - } - - virtual const TraceEvent& GetEventAt(size_t index) const OVERRIDE { - DCHECK(index < logged_events_.size()); - return logged_events_[index]; - } - - virtual size_t Size() const OVERRIDE { - return logged_events_.size(); + return NULL; } - virtual size_t Capacity() const OVERRIDE { - return kTraceEventVectorBufferSize; + virtual scoped_ptr<TraceBuffer> CloneForIteration() const OVERRIDE { + NOTIMPLEMENTED(); + return scoped_ptr<TraceBuffer>(); } private: + size_t in_flight_chunk_count_; size_t current_iteration_index_; - std::vector<TraceEvent> logged_events_; + ScopedVector<TraceBufferChunk> chunks_; DISALLOW_COPY_AND_ASSIGN(TraceBufferVector); }; -class TraceBufferDiscardsEvents : public TraceBuffer { - public: - virtual ~TraceBufferDiscardsEvents() { } +template <typename T> +void InitializeMetadataEvent(TraceEvent* trace_event, + int thread_id, + const char* metadata_name, const char* arg_name, + const T& value) { + if (!trace_event) + return; - virtual void AddEvent(const TraceEvent& event) OVERRIDE {} - virtual bool HasMoreEvents() const OVERRIDE { return false; } + int num_args = 1; + unsigned char arg_type; + unsigned long long arg_value; + ::trace_event_internal::SetTraceValue(value, &arg_type, &arg_value); + trace_event->Initialize(thread_id, + TimeTicks(), TimeTicks(), TRACE_EVENT_PHASE_METADATA, + &g_category_group_enabled[g_category_metadata], + metadata_name, ::trace_event_internal::kNoEventId, + num_args, &arg_name, &arg_type, &arg_value, NULL, + TRACE_EVENT_FLAG_NONE); +} - virtual const TraceEvent& NextEvent() OVERRIDE { - NOTREACHED(); - return *static_cast<TraceEvent*>(NULL); +class AutoThreadLocalBoolean { + public: + explicit AutoThreadLocalBoolean(ThreadLocalBoolean* thread_local_boolean) + : thread_local_boolean_(thread_local_boolean) { + DCHECK(!thread_local_boolean_->Get()); + thread_local_boolean_->Set(true); + } + ~AutoThreadLocalBoolean() { + thread_local_boolean_->Set(false); } - virtual bool IsFull() const OVERRIDE { return false; } + private: + ThreadLocalBoolean* thread_local_boolean_; + DISALLOW_COPY_AND_ASSIGN(AutoThreadLocalBoolean); +}; - virtual size_t CountEnabledByName( - const unsigned char* category, - const std::string& event_name) const OVERRIDE { - return 0; - } +} // namespace + +void TraceBufferChunk::Reset(uint32 new_seq) { + for (size_t i = 0; i < next_free_; ++i) + chunk_[i].Reset(); + next_free_ = 0; + seq_ = new_seq; +} - virtual size_t Size() const OVERRIDE { return 0; } +TraceEvent* TraceBufferChunk::AddTraceEvent(size_t* event_index) { + DCHECK(!IsFull()); + *event_index = next_free_++; + return &chunk_[*event_index]; +} - // As this buffer is never full, we can return any positive number. - virtual size_t Capacity() const OVERRIDE { return 1; } +scoped_ptr<TraceBufferChunk> TraceBufferChunk::Clone() const { + scoped_ptr<TraceBufferChunk> cloned_chunk(new TraceBufferChunk(seq_)); + cloned_chunk->next_free_ = next_free_; + for (size_t i = 0; i < next_free_; ++i) + cloned_chunk->chunk_[i].CopyFrom(chunk_[i]); + return cloned_chunk.Pass(); +} - virtual const TraceEvent& GetEventAt(size_t index) const OVERRIDE { - NOTREACHED(); - return *static_cast<TraceEvent*>(NULL); +// A helper class that allows the lock to be acquired in the middle of the scope +// and unlocks at the end of scope if locked. +class TraceLog::OptionalAutoLock { + public: + explicit OptionalAutoLock(Lock& lock) + : lock_(lock), + locked_(false) { + } + + ~OptionalAutoLock() { + if (locked_) + lock_.Release(); + } + + void EnsureAcquired() { + if (!locked_) { + lock_.Acquire(); + locked_ = true; + } } + + private: + Lock& lock_; + bool locked_; + DISALLOW_COPY_AND_ASSIGN(OptionalAutoLock); }; -} // namespace +// Use this function instead of TraceEventHandle constructor to keep the +// overhead of ScopedTracer (trace_event.h) constructor minimum. +void MakeHandle(uint32 chunk_seq, size_t chunk_index, size_t event_index, + TraceEventHandle* handle) { + DCHECK(chunk_seq); + DCHECK(chunk_index < (1u << 16)); + DCHECK(event_index < (1u << 16)); + handle->chunk_seq = chunk_seq; + handle->chunk_index = static_cast<uint16>(chunk_index); + handle->event_index = static_cast<uint16>(event_index); +} //////////////////////////////////////////////////////////////////////////////// // @@ -312,18 +477,42 @@ void CopyTraceEventParameter(char** buffer, } // namespace TraceEvent::TraceEvent() - : id_(0u), + : duration_(TimeDelta::FromInternalValue(-1)), + id_(0u), category_group_enabled_(NULL), name_(NULL), thread_id_(0), phase_(TRACE_EVENT_PHASE_BEGIN), flags_(0) { - arg_names_[0] = NULL; - arg_names_[1] = NULL; + for (int i = 0; i < kTraceMaxNumArgs; ++i) + arg_names_[i] = NULL; memset(arg_values_, 0, sizeof(arg_values_)); } -TraceEvent::TraceEvent( +TraceEvent::~TraceEvent() { +} + +void TraceEvent::CopyFrom(const TraceEvent& other) { + timestamp_ = other.timestamp_; + thread_timestamp_ = other.thread_timestamp_; + duration_ = other.duration_; + id_ = other.id_; + category_group_enabled_ = other.category_group_enabled_; + name_ = other.name_; + thread_id_ = other.thread_id_; + phase_ = other.phase_; + flags_ = other.flags_; + parameter_copy_storage_ = other.parameter_copy_storage_; + + for (int i = 0; i < kTraceMaxNumArgs; ++i) { + arg_names_[i] = other.arg_names_[i]; + arg_types_[i] = other.arg_types_[i]; + arg_values_[i] = other.arg_values_[i]; + convertable_values_[i] = other.convertable_values_[i]; + } +} + +void TraceEvent::Initialize( int thread_id, TimeTicks timestamp, TimeTicks thread_timestamp, @@ -335,16 +524,17 @@ TraceEvent::TraceEvent( const char** arg_names, const unsigned char* arg_types, const unsigned long long* arg_values, - scoped_ptr<ConvertableToTraceFormat> convertable_values[], - unsigned char flags) - : timestamp_(timestamp), - thread_timestamp_(thread_timestamp), - id_(id), - category_group_enabled_(category_group_enabled), - name_(name), - thread_id_(thread_id), - phase_(phase), - flags_(flags) { + const scoped_refptr<ConvertableToTraceFormat>* convertable_values, + unsigned char flags) { + timestamp_ = timestamp; + thread_timestamp_ = thread_timestamp; + duration_ = TimeDelta::FromInternalValue(-1); + id_ = id; + category_group_enabled_ = category_group_enabled; + name_ = name; + thread_id_ = thread_id; + phase_ = phase; + flags_ = flags; // Clamp num_args since it may have been set by a third_party library. num_args = (num_args > kTraceMaxNumArgs) ? kTraceMaxNumArgs : num_args; @@ -354,14 +544,14 @@ TraceEvent::TraceEvent( arg_types_[i] = arg_types[i]; if (arg_types[i] == TRACE_VALUE_TYPE_CONVERTABLE) - convertable_values_[i].reset(convertable_values[i].release()); + convertable_values_[i] = convertable_values[i]; else arg_values_[i].as_uint = arg_values[i]; } for (; i < kTraceMaxNumArgs; ++i) { arg_names_[i] = NULL; arg_values_[i].as_uint = 0u; - convertable_values_[i].reset(); + convertable_values_[i] = NULL; arg_types_[i] = TRACE_VALUE_TYPE_UINT; } @@ -409,68 +599,26 @@ TraceEvent::TraceEvent( } } -TraceEvent::TraceEvent(const TraceEvent& other) - : timestamp_(other.timestamp_), - thread_timestamp_(other.thread_timestamp_), - id_(other.id_), - category_group_enabled_(other.category_group_enabled_), - name_(other.name_), - thread_id_(other.thread_id_), - phase_(other.phase_), - flags_(other.flags_) { - parameter_copy_storage_ = other.parameter_copy_storage_; - - for (int i = 0; i < kTraceMaxNumArgs; ++i) { - arg_values_[i] = other.arg_values_[i]; - arg_names_[i] = other.arg_names_[i]; - arg_types_[i] = other.arg_types_[i]; - - if (arg_types_[i] == TRACE_VALUE_TYPE_CONVERTABLE) { - convertable_values_[i].reset( - const_cast<TraceEvent*>(&other)->convertable_values_[i].release()); - } else { - convertable_values_[i].reset(); - } - } +void TraceEvent::Reset() { + // Only reset fields that won't be initialized in Initialize(), or that may + // hold references to other objects. + duration_ = TimeDelta::FromInternalValue(-1); + parameter_copy_storage_ = NULL; + for (int i = 0; i < kTraceMaxNumArgs; ++i) + convertable_values_[i] = NULL; } -TraceEvent& TraceEvent::operator=(const TraceEvent& other) { - if (this == &other) - return *this; - - timestamp_ = other.timestamp_; - thread_timestamp_ = other.thread_timestamp_; - id_ = other.id_; - category_group_enabled_ = other.category_group_enabled_; - name_ = other.name_; - parameter_copy_storage_ = other.parameter_copy_storage_; - thread_id_ = other.thread_id_; - phase_ = other.phase_; - flags_ = other.flags_; - - for (int i = 0; i < kTraceMaxNumArgs; ++i) { - arg_values_[i] = other.arg_values_[i]; - arg_names_[i] = other.arg_names_[i]; - arg_types_[i] = other.arg_types_[i]; - - if (arg_types_[i] == TRACE_VALUE_TYPE_CONVERTABLE) { - convertable_values_[i].reset( - const_cast<TraceEvent*>(&other)->convertable_values_[i].release()); - } else { - convertable_values_[i].reset(); - } - } - return *this; -} - -TraceEvent::~TraceEvent() { +void TraceEvent::UpdateDuration(const TimeTicks& now, + const TimeTicks& thread_now) { + DCHECK(duration_.ToInternalValue() == -1); + duration_ = now - timestamp_; + thread_duration_ = thread_now - thread_timestamp_; } // static void TraceEvent::AppendValueAsJSON(unsigned char type, TraceEvent::TraceValue value, std::string* out) { - std::string::size_type start_pos; switch (type) { case TRACE_VALUE_TYPE_BOOL: *out += value.as_bool ? "true" : "false"; @@ -481,9 +629,30 @@ void TraceEvent::AppendValueAsJSON(unsigned char type, case TRACE_VALUE_TYPE_INT: StringAppendF(out, "%" PRId64, static_cast<int64>(value.as_int)); break; - case TRACE_VALUE_TYPE_DOUBLE: - StringAppendF(out, "%f", value.as_double); + case TRACE_VALUE_TYPE_DOUBLE: { + // FIXME: base/json/json_writer.cc is using the same code, + // should be made into a common method. + std::string real = DoubleToString(value.as_double); + // Ensure that the number has a .0 if there's no decimal or 'e'. This + // makes sure that when we read the JSON back, it's interpreted as a + // real rather than an int. + if (real.find('.') == std::string::npos && + real.find('e') == std::string::npos && + real.find('E') == std::string::npos) { + real.append(".0"); + } + // The JSON spec requires that non-integer values in the range (-1,1) + // have a zero before the decimal point - ".52" is not valid, "0.52" is. + if (real[0] == '.') { + real.insert(0, "0"); + } else if (real.length() > 1 && real[0] == '-' && real[1] == '.') { + // "-.1" bad "-0.1" good + real.insert(1, "0"); + } + + StringAppendF(out, "%s", real.c_str()); break; + } case TRACE_VALUE_TYPE_POINTER: // JSON only supports double and int numbers. // So as not to lose bits from a 64-bit pointer, output as a hex string. @@ -493,17 +662,7 @@ void TraceEvent::AppendValueAsJSON(unsigned char type, break; case TRACE_VALUE_TYPE_STRING: case TRACE_VALUE_TYPE_COPY_STRING: - *out += "\""; - start_pos = out->size(); - *out += value.as_string ? value.as_string : "NULL"; - // insert backslash before special characters for proper json format. - while ((start_pos = out->find_first_of("\\\"", start_pos)) != - std::string::npos) { - out->insert(start_pos, 1, '\\'); - // skip inserted escape character and following character. - start_pos += 2; - } - *out += "\""; + EscapeJSONString(value.as_string ? value.as_string : "NULL", true, out); break; default: NOTREACHED() << "Don't know how to print this value"; @@ -541,6 +700,17 @@ void TraceEvent::AppendAsJSON(std::string* out) const { } *out += "}"; + if (phase_ == TRACE_EVENT_PHASE_COMPLETE) { + int64 duration = duration_.ToInternalValue(); + if (duration != -1) + StringAppendF(out, ",\"dur\":%" PRId64, duration); + if (!thread_timestamp_.is_null()) { + int64 thread_duration = thread_duration_.ToInternalValue(); + if (thread_duration != -1) + StringAppendF(out, ",\"tdur\":%" PRId64, thread_duration); + } + } + // Output tts if thread_timestamp is valid. if (!thread_timestamp_.is_null()) { int64 thread_time_int64 = thread_timestamp_.ToInternalValue(); @@ -669,10 +839,10 @@ class TraceSamplingThread : public PlatformThread::Delegate { // Implementation of PlatformThread::Delegate: virtual void ThreadMain() OVERRIDE; - static void DefaultSampleCallback(TraceBucketData* bucekt_data); + static void DefaultSamplingCallback(TraceBucketData* bucekt_data); void Stop(); - void InstallWaitableEventForSamplingTesting(WaitableEvent* waitable_event); + void WaitSamplingEventForTesting(); private: friend class TraceLog; @@ -689,14 +859,14 @@ class TraceSamplingThread : public PlatformThread::Delegate { const char** name); std::vector<TraceBucketData> sample_buckets_; bool thread_running_; - scoped_ptr<CancellationFlag> cancellation_flag_; - scoped_ptr<WaitableEvent> waitable_event_for_testing_; + CancellationFlag cancellation_flag_; + WaitableEvent waitable_event_for_testing_; }; TraceSamplingThread::TraceSamplingThread() - : thread_running_(false) { - cancellation_flag_.reset(new CancellationFlag); + : thread_running_(false), + waitable_event_for_testing_(false, false) { } TraceSamplingThread::~TraceSamplingThread() { @@ -706,17 +876,17 @@ void TraceSamplingThread::ThreadMain() { PlatformThread::SetName("Sampling Thread"); thread_running_ = true; const int kSamplingFrequencyMicroseconds = 1000; - while (!cancellation_flag_->IsSet()) { + while (!cancellation_flag_.IsSet()) { PlatformThread::Sleep( TimeDelta::FromMicroseconds(kSamplingFrequencyMicroseconds)); GetSamples(); - if (waitable_event_for_testing_.get()) - waitable_event_for_testing_->Signal(); + waitable_event_for_testing_.Signal(); } } // static -void TraceSamplingThread::DefaultSampleCallback(TraceBucketData* bucket_data) { +void TraceSamplingThread::DefaultSamplingCallback( + TraceBucketData* bucket_data) { TRACE_EVENT_API_ATOMIC_WORD category_and_name = TRACE_EVENT_API_ATOMIC_LOAD(*bucket_data->bucket); if (!category_and_name) @@ -742,6 +912,9 @@ void TraceSamplingThread::RegisterSampleBucket( TRACE_EVENT_API_ATOMIC_WORD* bucket, const char* const name, TraceSampleCallback callback) { + // Access to sample_buckets_ doesn't cause races with the sampling thread + // that uses the sample_buckets_, because it is guaranteed that + // RegisterSampleBucket is called before the sampling thread is created. DCHECK(!thread_running_); sample_buckets_.push_back(TraceBucketData(bucket, name, callback)); } @@ -755,15 +928,13 @@ void TraceSamplingThread::ExtractCategoryAndName(const char* combined, } void TraceSamplingThread::Stop() { - cancellation_flag_->Set(); + cancellation_flag_.Set(); } -void TraceSamplingThread::InstallWaitableEventForSamplingTesting( - WaitableEvent* waitable_event) { - waitable_event_for_testing_.reset(waitable_event); +void TraceSamplingThread::WaitSamplingEventForTesting() { + waitable_event_for_testing_.Wait(); } - TraceBucketData::TraceBucketData(base::subtle::AtomicWord* bucket, const char* name, TraceSampleCallback callback) @@ -787,35 +958,48 @@ class TraceLog::ThreadLocalEventBuffer ThreadLocalEventBuffer(TraceLog* trace_log); virtual ~ThreadLocalEventBuffer(); - void AddEvent(const TraceEvent& event, NotificationHelper* notifier); + TraceEvent* AddTraceEvent(TraceEventHandle* handle); + void ReportOverhead(const TimeTicks& event_timestamp, const TimeTicks& event_thread_timestamp); + TraceEvent* GetEventByHandle(TraceEventHandle handle) { + if (!chunk_ || handle.chunk_seq != chunk_->seq() || + handle.chunk_index != chunk_index_) + return NULL; + + return chunk_->GetEventAt(handle.event_index); + } + + int generation() const { return generation_; } + private: // MessageLoop::DestructionObserver virtual void WillDestroyCurrentMessageLoop() OVERRIDE; - void FlushWhileLocked(NotificationHelper* notifier); + void FlushWhileLocked(); - void CheckThisIsCurrentBuffer() { + void CheckThisIsCurrentBuffer() const { DCHECK(trace_log_->thread_local_event_buffer_.Get() == this); } // Since TraceLog is a leaky singleton, trace_log_ will always be valid // as long as the thread exists. TraceLog* trace_log_; - std::vector<TraceEvent> logged_events_; + scoped_ptr<TraceBufferChunk> chunk_; + size_t chunk_index_; int event_count_; TimeDelta overhead_; + int generation_; DISALLOW_COPY_AND_ASSIGN(ThreadLocalEventBuffer); }; TraceLog::ThreadLocalEventBuffer::ThreadLocalEventBuffer(TraceLog* trace_log) : trace_log_(trace_log), - event_count_(0) { - logged_events_.reserve(kTraceEventThreadLocalBufferSize); - + chunk_index_(0), + event_count_(0), + generation_(trace_log->generation()) { // ThreadLocalEventBuffer is created only if the thread has a message loop, so // the following message_loop won't be NULL. MessageLoop* message_loop = MessageLoop::current(); @@ -834,67 +1018,68 @@ TraceLog::ThreadLocalEventBuffer::~ThreadLocalEventBuffer() { // - the thread has no message loop; // - trace_event_overhead is disabled. if (event_count_) { - const char* arg_names[2] = { "event_count", "average_overhead" }; - unsigned char arg_types[2]; - unsigned long long arg_values[2]; - trace_event_internal::SetTraceValue( - event_count_, &arg_types[0], &arg_values[0]); - trace_event_internal::SetTraceValue( - overhead_.InMillisecondsF() / event_count_, - &arg_types[1], &arg_values[1]); - logged_events_.push_back(TraceEvent( - static_cast<int>(PlatformThread::CurrentId()), - TimeTicks(), TimeTicks(), TRACE_EVENT_PHASE_METADATA, - &g_category_group_enabled[g_category_metadata], - "trace_event_overhead", trace_event_internal::kNoEventId, - 2, arg_names, arg_types, arg_values, NULL, - TRACE_EVENT_FLAG_NONE)); - } - - NotificationHelper notifier(trace_log_); + InitializeMetadataEvent(AddTraceEvent(NULL), + static_cast<int>(base::PlatformThread::CurrentId()), + "overhead", "average_overhead", + overhead_.InMillisecondsF() / event_count_); + } + { AutoLock lock(trace_log_->lock_); - FlushWhileLocked(¬ifier); + FlushWhileLocked(); trace_log_->thread_message_loops_.erase(MessageLoop::current()); } trace_log_->thread_local_event_buffer_.Set(NULL); - notifier.SendNotificationIfAny(); } -void TraceLog::ThreadLocalEventBuffer::AddEvent(const TraceEvent& event, - NotificationHelper* notifier) { +TraceEvent* TraceLog::ThreadLocalEventBuffer::AddTraceEvent( + TraceEventHandle* handle) { CheckThisIsCurrentBuffer(); - logged_events_.push_back(event); - if (logged_events_.size() >= kTraceEventThreadLocalBufferSize) { + + if (chunk_ && chunk_->IsFull()) { + AutoLock lock(trace_log_->lock_); + FlushWhileLocked(); + chunk_.reset(); + } + if (!chunk_) { AutoLock lock(trace_log_->lock_); - FlushWhileLocked(notifier); + chunk_ = trace_log_->logged_events_->GetChunk(&chunk_index_); + trace_log_->CheckIfBufferIsFullWhileLocked(); } + if (!chunk_) + return NULL; + + size_t event_index; + TraceEvent* trace_event = chunk_->AddTraceEvent(&event_index); + if (trace_event && handle) + MakeHandle(chunk_->seq(), chunk_index_, event_index, handle); + + return trace_event; } void TraceLog::ThreadLocalEventBuffer::ReportOverhead( - const TimeTicks& event_timestamp, const TimeTicks& event_thread_timestamp) { + const TimeTicks& event_timestamp, + const TimeTicks& event_thread_timestamp) { if (!g_category_group_enabled[g_category_trace_event_overhead]) return; + CheckThisIsCurrentBuffer(); + event_count_++; - TimeTicks now = - TimeTicks::NowFromSystemTraceTime() - trace_log_->time_offset_; + TimeTicks thread_now = ThreadNow(); + TimeTicks now = trace_log_->OffsetNow(); TimeDelta overhead = now - event_timestamp; if (overhead.InMicroseconds() >= kOverheadReportThresholdInMicroseconds) { - int thread_id = static_cast<int>(PlatformThread::CurrentId()); - // TODO(wangxianzhu): Use X event when it's ready. - logged_events_.push_back(TraceEvent( - thread_id, event_timestamp, event_thread_timestamp, - TRACE_EVENT_PHASE_BEGIN, - &g_category_group_enabled[g_category_trace_event_overhead], - "overhead", - 0, 0, NULL, NULL, NULL, NULL, 0)); - logged_events_.push_back(TraceEvent( - thread_id, now, ThreadNow(), - TRACE_EVENT_PHASE_END, - &g_category_group_enabled[g_category_trace_event_overhead], - "overhead", - 0, 0, NULL, NULL, NULL, NULL, 0)); + TraceEvent* trace_event = AddTraceEvent(NULL); + if (trace_event) { + trace_event->Initialize( + static_cast<int>(PlatformThread::CurrentId()), + event_timestamp, event_thread_timestamp, + TRACE_EVENT_PHASE_COMPLETE, + &g_category_group_enabled[g_category_trace_event_overhead], + "overhead", 0, 0, NULL, NULL, NULL, NULL, 0); + trace_event->UpdateDuration(now, thread_now); + } } overhead_ += overhead; } @@ -903,37 +1088,17 @@ void TraceLog::ThreadLocalEventBuffer::WillDestroyCurrentMessageLoop() { delete this; } -void TraceLog::ThreadLocalEventBuffer::FlushWhileLocked( - NotificationHelper* notifier) { - trace_log_->lock_.AssertAcquired(); - for (size_t i = 0; i < logged_events_.size(); ++i) { - trace_log_->AddEventToMainBufferWhileLocked(logged_events_[i]); - } - logged_events_.resize(0); - trace_log_->CheckIfBufferIsFullWhileLocked(notifier); -} - -TraceLog::NotificationHelper::NotificationHelper(TraceLog* trace_log) - : trace_log_(trace_log), - notification_(0) { -} - -TraceLog::NotificationHelper::~NotificationHelper() { -} - -void TraceLog::NotificationHelper::AddNotificationWhileLocked( - int notification) { - trace_log_->lock_.AssertAcquired(); - if (trace_log_->notification_callback_.is_null()) +void TraceLog::ThreadLocalEventBuffer::FlushWhileLocked() { + if (!chunk_) return; - if (notification_ == 0) - callback_copy_ = trace_log_->notification_callback_; - notification_ |= notification; -} -void TraceLog::NotificationHelper::SendNotificationIfAny() { - if (notification_) - callback_copy_.Run(notification_); + trace_log_->lock_.AssertAcquired(); + if (trace_log_->CheckGeneration(generation_)) { + // Return the chunk to the buffer only if the generation matches, + trace_log_->logged_events_->ReturnChunk(chunk_index_, chunk_.Pass()); + } + // Otherwise this method may be called from the destructor, or TraceLog will + // find the generation mismatch and delete this buffer soon. } // static @@ -941,36 +1106,9 @@ TraceLog* TraceLog::GetInstance() { return Singleton<TraceLog, LeakySingletonTraits<TraceLog> >::get(); } -// static -// Note, if you add more options here you also need to update: -// content/browser/devtools/devtools_tracing_handler:TraceOptionsFromString -TraceLog::Options TraceLog::TraceOptionsFromString(const std::string& options) { - std::vector<std::string> split; - base::SplitString(options, ',', &split); - int ret = 0; - for (std::vector<std::string>::iterator iter = split.begin(); - iter != split.end(); - ++iter) { - if (*iter == kRecordUntilFull) { - ret |= RECORD_UNTIL_FULL; - } else if (*iter == kRecordContinuously) { - ret |= RECORD_CONTINUOUSLY; - } else if (*iter == kEnableSampling) { - ret |= ENABLE_SAMPLING; - } else { - NOTREACHED(); // Unknown option provided. - } - } - if (!(ret & RECORD_UNTIL_FULL) && !(ret & RECORD_CONTINUOUSLY)) - ret |= RECORD_UNTIL_FULL; // Default when no options are specified. - - return static_cast<Options>(ret); -} - TraceLog::TraceLog() - : enable_count_(0), + : enabled_(false), num_traces_recorded_(0), - buffer_is_full_(0), event_callback_(0), dispatching_to_observer_list_(false), process_sort_index_(0), @@ -980,7 +1118,10 @@ TraceLog::TraceLog() trace_options_(RECORD_UNTIL_FULL), sampling_thread_handle_(0), category_filter_(CategoryFilter::kDefaultCategoryFilterString), - flush_count_(0) { + event_callback_category_filter_( + CategoryFilter::kDefaultCategoryFilterString), + thread_shared_chunk_index_(0), + generation_(0) { // Trace is enabled or disabled on one thread while other threads are // accessing the enabled flag. We don't care whether edge-case events are // traced or not, so we allow races on the enabled flag to keep the trace @@ -1001,18 +1142,22 @@ TraceLog::TraceLog() // NaCl also shouldn't access the command line. if (CommandLine::InitializedForCurrentProcess() && CommandLine::ForCurrentProcess()->HasSwitch(switches::kTraceToConsole)) { - std::string category_string = - CommandLine::ForCurrentProcess()->GetSwitchValueASCII( - switches::kTraceToConsole); - - if (category_string.empty()) - category_string = "*"; + std::string filter = CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + switches::kTraceToConsole); + if (filter.empty()) { + filter = kEchoToConsoleCategoryFilter; + } else { + filter.append(","); + filter.append(kEchoToConsoleCategoryFilter); + } - SetEnabled(CategoryFilter(category_string), ECHO_TO_CONSOLE); + LOG(ERROR) << "Start " << switches::kTraceToConsole + << " with CategoryFilter '" << filter << "'."; + SetEnabled(CategoryFilter(filter), ECHO_TO_CONSOLE); } #endif - logged_events_.reset(GetTraceBuffer()); + logged_events_.reset(CreateTraceBuffer()); } TraceLog::~TraceLog() { @@ -1045,9 +1190,14 @@ const char* TraceLog::GetCategoryGroupName( } void TraceLog::UpdateCategoryGroupEnabledFlag(int category_index) { - bool is_enabled = enable_count_ && category_filter_.IsCategoryGroupEnabled( - g_category_groups[category_index]); - SetCategoryGroupEnabled(category_index, is_enabled); + unsigned char enabled_flag = 0; + const char* category_group = g_category_groups[category_index]; + if (enabled_ && category_filter_.IsCategoryGroupEnabled(category_group)) + enabled_flag |= ENABLED_FOR_RECORDING; + if (event_callback_ && + event_callback_category_filter_.IsCategoryGroupEnabled(category_group)) + enabled_flag |= ENABLED_FOR_EVENT_CALLBACK; + g_category_group_enabled[category_index] = enabled_flag; } void TraceLog::UpdateCategoryGroupEnabledFlags() { @@ -1055,22 +1205,6 @@ void TraceLog::UpdateCategoryGroupEnabledFlags() { UpdateCategoryGroupEnabledFlag(i); } -void TraceLog::SetCategoryGroupEnabled(int category_index, bool is_enabled) { - g_category_group_enabled[category_index] = - is_enabled ? CATEGORY_GROUP_ENABLED : 0; - -#if defined(OS_ANDROID) - ApplyATraceEnabledFlag(&g_category_group_enabled[category_index]); -#endif -} - -bool TraceLog::IsCategoryGroupEnabled( - const unsigned char* category_group_enabled) { - // On Android, ATrace and normal trace can be enabled independently. - // This function checks if the normal trace is enabled. - return *category_group_enabled & CATEGORY_GROUP_ENABLED; -} - const unsigned char* TraceLog::GetCategoryGroupEnabledInternal( const char* category_group) { DCHECK(!strchr(category_group, '"')) << @@ -1132,7 +1266,7 @@ void TraceLog::SetEnabled(const CategoryFilter& category_filter, Options old_options = trace_options(); - if (enable_count_++ > 0) { + if (enabled_) { if (options != old_options) { DLOG(ERROR) << "Attemting to re-enable tracing with a different " << "set of options."; @@ -1143,37 +1277,38 @@ void TraceLog::SetEnabled(const CategoryFilter& category_filter, return; } - if (options != old_options) { - subtle::NoBarrier_Store(&trace_options_, options); - logged_events_.reset(GetTraceBuffer()); - subtle::NoBarrier_Store(&buffer_is_full_, 0); - } - if (dispatching_to_observer_list_) { DLOG(ERROR) << "Cannot manipulate TraceLog::Enabled state from an observer."; return; } + enabled_ = true; + + if (options != old_options) { + subtle::NoBarrier_Store(&trace_options_, options); + UseNextTraceBuffer(); + } + num_traces_recorded_++; category_filter_ = CategoryFilter(category_filter); UpdateCategoryGroupEnabledFlags(); - if (options & ENABLE_SAMPLING) { + if ((options & ENABLE_SAMPLING) || (options & MONITOR_SAMPLING)) { sampling_thread_.reset(new TraceSamplingThread); sampling_thread_->RegisterSampleBucket( &g_trace_state[0], "bucket0", - Bind(&TraceSamplingThread::DefaultSampleCallback)); + Bind(&TraceSamplingThread::DefaultSamplingCallback)); sampling_thread_->RegisterSampleBucket( &g_trace_state[1], "bucket1", - Bind(&TraceSamplingThread::DefaultSampleCallback)); + Bind(&TraceSamplingThread::DefaultSamplingCallback)); sampling_thread_->RegisterSampleBucket( &g_trace_state[2], "bucket2", - Bind(&TraceSamplingThread::DefaultSampleCallback)); + Bind(&TraceSamplingThread::DefaultSamplingCallback)); if (!PlatformThread::Create( 0, sampling_thread_.get(), &sampling_thread_handle_)) { DCHECK(false) << "failed to create thread"; @@ -1193,61 +1328,63 @@ void TraceLog::SetEnabled(const CategoryFilter& category_filter, } } -const CategoryFilter& TraceLog::GetCurrentCategoryFilter() { +CategoryFilter TraceLog::GetCurrentCategoryFilter() { AutoLock lock(lock_); - DCHECK(enable_count_ > 0); return category_filter_; } void TraceLog::SetDisabled() { - std::vector<EnabledStateObserver*> observer_list; - { - AutoLock lock(lock_); - DCHECK(enable_count_ > 0); + AutoLock lock(lock_); + SetDisabledWhileLocked(); +} - if (--enable_count_ != 0) - return; +void TraceLog::SetDisabledWhileLocked() { + lock_.AssertAcquired(); - if (dispatching_to_observer_list_) { - DLOG(ERROR) - << "Cannot manipulate TraceLog::Enabled state from an observer."; - return; - } + if (!enabled_) + return; - if (sampling_thread_.get()) { - // Stop the sampling thread. - sampling_thread_->Stop(); - lock_.Release(); - PlatformThread::Join(sampling_thread_handle_); - lock_.Acquire(); - sampling_thread_handle_ = PlatformThreadHandle(); - sampling_thread_.reset(); - } + if (dispatching_to_observer_list_) { + DLOG(ERROR) + << "Cannot manipulate TraceLog::Enabled state from an observer."; + return; + } - category_filter_.Clear(); - subtle::NoBarrier_Store(&watch_category_, 0); - watch_event_name_ = ""; - UpdateCategoryGroupEnabledFlags(); - AddMetadataEvents(); + enabled_ = false; - dispatching_to_observer_list_ = true; - observer_list = enabled_state_observer_list_; + if (sampling_thread_.get()) { + // Stop the sampling thread. + sampling_thread_->Stop(); + lock_.Release(); + PlatformThread::Join(sampling_thread_handle_); + lock_.Acquire(); + sampling_thread_handle_ = PlatformThreadHandle(); + sampling_thread_.reset(); } - // Dispatch to observers outside the lock in case the observer triggers a - // trace event. - for (size_t i = 0; i < observer_list.size(); ++i) - observer_list[i]->OnTraceLogDisabled(); + category_filter_.Clear(); + subtle::NoBarrier_Store(&watch_category_, 0); + watch_event_name_ = ""; + UpdateCategoryGroupEnabledFlags(); + AddMetadataEventsWhileLocked(); + + dispatching_to_observer_list_ = true; + std::vector<EnabledStateObserver*> observer_list = + enabled_state_observer_list_; { - AutoLock lock(lock_); - dispatching_to_observer_list_ = false; + // Dispatch to observers outside the lock in case the observer triggers a + // trace event. + AutoUnlock unlock(lock_); + for (size_t i = 0; i < observer_list.size(); ++i) + observer_list[i]->OnTraceLogDisabled(); } + dispatching_to_observer_list_ = false; } int TraceLog::GetNumTracesRecorded() { AutoLock lock(lock_); - if (enable_count_ == 0) + if (!enabled_) return -1; return num_traces_recorded_; } @@ -1274,47 +1411,75 @@ bool TraceLog::HasEnabledStateObserver(EnabledStateObserver* listener) const { } float TraceLog::GetBufferPercentFull() const { + AutoLock lock(lock_); return static_cast<float>(static_cast<double>(logged_events_->Size()) / logged_events_->Capacity()); } -void TraceLog::SetNotificationCallback( - const TraceLog::NotificationCallback& cb) { +bool TraceLog::BufferIsFull() const { AutoLock lock(lock_); - notification_callback_ = cb; + return logged_events_->IsFull(); } -TraceBuffer* TraceLog::GetTraceBuffer() { +TraceBuffer* TraceLog::CreateTraceBuffer() { Options options = trace_options(); if (options & RECORD_CONTINUOUSLY) - return new TraceBufferRingBuffer(); + return new TraceBufferRingBuffer(kTraceEventRingBufferChunks); + else if (options & MONITOR_SAMPLING) + return new TraceBufferRingBuffer(kMonitorTraceEventBufferChunks); else if (options & ECHO_TO_CONSOLE) - return new TraceBufferDiscardsEvents(); + return new TraceBufferRingBuffer(kEchoToConsoleTraceEventBufferChunks); return new TraceBufferVector(); } -void TraceLog::AddEventToMainBufferWhileLocked(const TraceEvent& trace_event) { - // Don't check buffer_is_full_ because we want the remaining thread-local - // events to be flushed into the main buffer with this method, otherwise - // we may lose some early events of a thread that generates events sparsely. +TraceEvent* TraceLog::AddEventToThreadSharedChunkWhileLocked( + TraceEventHandle* handle, bool check_buffer_is_full) { lock_.AssertAcquired(); - logged_events_->AddEvent(trace_event); + + if (thread_shared_chunk_ && thread_shared_chunk_->IsFull()) { + logged_events_->ReturnChunk(thread_shared_chunk_index_, + thread_shared_chunk_.Pass()); + } + + if (!thread_shared_chunk_) { + thread_shared_chunk_ = logged_events_->GetChunk( + &thread_shared_chunk_index_); + if (check_buffer_is_full) + CheckIfBufferIsFullWhileLocked(); + } + if (!thread_shared_chunk_) + return NULL; + + size_t event_index; + TraceEvent* trace_event = thread_shared_chunk_->AddTraceEvent(&event_index); + if (trace_event && handle) { + MakeHandle(thread_shared_chunk_->seq(), thread_shared_chunk_index_, + event_index, handle); + } + return trace_event; } -void TraceLog::CheckIfBufferIsFullWhileLocked(NotificationHelper* notifier) { +void TraceLog::CheckIfBufferIsFullWhileLocked() { lock_.AssertAcquired(); - if (!subtle::NoBarrier_Load(&buffer_is_full_) && logged_events_->IsFull()) { - subtle::NoBarrier_Store(&buffer_is_full_, - static_cast<subtle::AtomicWord>(1)); - notifier->AddNotificationWhileLocked(TRACE_BUFFER_FULL); - } + if (logged_events_->IsFull()) + SetDisabledWhileLocked(); } -void TraceLog::SetEventCallback(EventCallback cb) { +void TraceLog::SetEventCallbackEnabled(const CategoryFilter& category_filter, + EventCallback cb) { + AutoLock lock(lock_); subtle::NoBarrier_Store(&event_callback_, reinterpret_cast<subtle::AtomicWord>(cb)); + event_callback_category_filter_ = category_filter; + UpdateCategoryGroupEnabledFlags(); }; +void TraceLog::SetEventCallbackDisabled() { + AutoLock lock(lock_); + subtle::NoBarrier_Store(&event_callback_, 0); + UpdateCategoryGroupEnabledFlags(); +} + // Flush() works as the following: // 1. Flush() is called in threadA whose message loop is saved in // flush_message_loop_proxy_; @@ -1328,8 +1493,8 @@ void TraceLog::SetEventCallback(EventCallback cb) { void TraceLog::Flush(const TraceLog::OutputCallback& cb) { if (IsEnabled()) { // Can't flush when tracing is enabled because otherwise PostTask would - // - it generates more trace events; - // - it deschedules the calling thread on some platforms causing inaccurate + // - generate more trace events; + // - deschedule the calling thread on some platforms causing inaccurate // timing of the trace events. scoped_refptr<RefCountedString> empty_result = new RefCountedString; if (!cb.is_null()) @@ -1338,114 +1503,163 @@ void TraceLog::Flush(const TraceLog::OutputCallback& cb) { return; } - int flush_count; + int generation = this->generation(); { AutoLock lock(lock_); - flush_count = ++flush_count_; DCHECK(!flush_message_loop_proxy_.get()); flush_message_loop_proxy_ = MessageLoopProxy::current(); DCHECK(!thread_message_loops_.size() || flush_message_loop_proxy_.get()); flush_output_callback_ = cb; + if (thread_shared_chunk_) { + logged_events_->ReturnChunk(thread_shared_chunk_index_, + thread_shared_chunk_.Pass()); + } + if (thread_message_loops_.size()) { for (hash_set<MessageLoop*>::const_iterator it = thread_message_loops_.begin(); it != thread_message_loops_.end(); ++it) { (*it)->PostTask( FROM_HERE, - Bind(&TraceLog::FlushCurrentThread, Unretained(this), flush_count)); + Bind(&TraceLog::FlushCurrentThread, Unretained(this), generation)); } flush_message_loop_proxy_->PostDelayedTask( FROM_HERE, - Bind(&TraceLog::OnFlushTimeout, Unretained(this), flush_count), + Bind(&TraceLog::OnFlushTimeout, Unretained(this), generation), TimeDelta::FromMilliseconds(kThreadFlushTimeoutMs)); return; } } - FinishFlush(flush_count); + FinishFlush(generation); } -void TraceLog::FinishFlush(int flush_count) { - scoped_ptr<TraceBuffer> previous_logged_events; - OutputCallback flush_output_callback; - - { - AutoLock lock(lock_); - if (flush_count != flush_count_) - return; - - previous_logged_events.swap(logged_events_); - logged_events_.reset(GetTraceBuffer()); - subtle::NoBarrier_Store(&buffer_is_full_, 0); - flush_message_loop_proxy_ = NULL; - flush_output_callback = flush_output_callback_; - flush_output_callback_.Reset(); - } +void TraceLog::ConvertTraceEventsToTraceFormat( + scoped_ptr<TraceBuffer> logged_events, + const TraceLog::OutputCallback& flush_output_callback) { if (flush_output_callback.is_null()) return; - bool has_more_events = previous_logged_events->HasMoreEvents(); // The callback need to be called at least once even if there is no events // to let the caller know the completion of flush. + bool has_more_events = true; do { scoped_refptr<RefCountedString> json_events_str_ptr = new RefCountedString(); - for (size_t i = 0; has_more_events && i < kTraceEventBatchSize; ++i) { - if (i > 0) - *(&(json_events_str_ptr->data())) += ","; - - previous_logged_events->NextEvent().AppendAsJSON( - &(json_events_str_ptr->data())); - - has_more_events = previous_logged_events->HasMoreEvents(); + for (size_t i = 0; i < kTraceEventBatchChunks; ++i) { + const TraceBufferChunk* chunk = logged_events->NextChunk(); + if (!chunk) { + has_more_events = false; + break; + } + for (size_t j = 0; j < chunk->size(); ++j) { + if (i > 0 || j > 0) + json_events_str_ptr->data().append(","); + chunk->GetEventAt(j)->AppendAsJSON(&(json_events_str_ptr->data())); + } } flush_output_callback.Run(json_events_str_ptr, has_more_events); } while (has_more_events); } +void TraceLog::FinishFlush(int generation) { + scoped_ptr<TraceBuffer> previous_logged_events; + OutputCallback flush_output_callback; + + if (!CheckGeneration(generation)) + return; + + { + AutoLock lock(lock_); + + previous_logged_events.swap(logged_events_); + UseNextTraceBuffer(); + thread_message_loops_.clear(); + + flush_message_loop_proxy_ = NULL; + flush_output_callback = flush_output_callback_; + flush_output_callback_.Reset(); + } + + ConvertTraceEventsToTraceFormat(previous_logged_events.Pass(), + flush_output_callback); +} + // Run in each thread holding a local event buffer. -void TraceLog::FlushCurrentThread(int flush_count) { +void TraceLog::FlushCurrentThread(int generation) { { AutoLock lock(lock_); - if (flush_count != flush_count_ || !flush_message_loop_proxy_) { + if (!CheckGeneration(generation) || !flush_message_loop_proxy_) { // This is late. The corresponding flush has finished. return; } } + // This will flush the thread local buffer. delete thread_local_event_buffer_.Get(); + AutoLock lock(lock_); + if (!CheckGeneration(generation) || !flush_message_loop_proxy_ || + thread_message_loops_.size()) + return; + + flush_message_loop_proxy_->PostTask( + FROM_HERE, + Bind(&TraceLog::FinishFlush, Unretained(this), generation)); +} + +void TraceLog::OnFlushTimeout(int generation) { { AutoLock lock(lock_); - if (flush_count != flush_count_ || !flush_message_loop_proxy_ || - thread_message_loops_.size()) + if (!CheckGeneration(generation) || !flush_message_loop_proxy_) { + // Flush has finished before timeout. return; + } - flush_message_loop_proxy_->PostTask( - FROM_HERE, - Bind(&TraceLog::FinishFlush, Unretained(this), flush_count)); + LOG(WARNING) << + "The following threads haven't finished flush in time. " + "If this happens stably for some thread, please call " + "TraceLog::GetInstance()->SetCurrentThreadBlocksMessageLoop() from " + "the thread to avoid its trace events from being lost."; + for (hash_set<MessageLoop*>::const_iterator it = + thread_message_loops_.begin(); + it != thread_message_loops_.end(); ++it) { + LOG(WARNING) << "Thread: " << (*it)->thread_name(); + } } + FinishFlush(generation); } -void TraceLog::OnFlushTimeout(int flush_count) { +void TraceLog::FlushButLeaveBufferIntact( + const TraceLog::OutputCallback& flush_output_callback) { + scoped_ptr<TraceBuffer> previous_logged_events; { AutoLock lock(lock_); - if (flush_count != flush_count_ || !flush_message_loop_proxy_) { - // Flush has finished before timeout. - return; + AddMetadataEventsWhileLocked(); + if (thread_shared_chunk_) { + // Return the chunk to the main buffer to flush the sampling data. + logged_events_->ReturnChunk(thread_shared_chunk_index_, + thread_shared_chunk_.Pass()); } + previous_logged_events = logged_events_->CloneForIteration().Pass(); + } // release lock - LOG(WARNING) << thread_message_loops_.size() << " threads haven't finished" - << " flush in time. Discarding remaining events of them"; - } - FinishFlush(flush_count); + ConvertTraceEventsToTraceFormat(previous_logged_events.Pass(), + flush_output_callback); } -void TraceLog::AddTraceEvent( +void TraceLog::UseNextTraceBuffer() { + logged_events_.reset(CreateTraceBuffer()); + subtle::NoBarrier_AtomicIncrement(&generation_, 1); + thread_shared_chunk_.reset(); + thread_shared_chunk_index_ = 0; +} + +TraceEventHandle TraceLog::AddTraceEvent( char phase, const unsigned char* category_group_enabled, const char* name, @@ -1454,17 +1668,18 @@ void TraceLog::AddTraceEvent( const char** arg_names, const unsigned char* arg_types, const unsigned long long* arg_values, - scoped_ptr<ConvertableToTraceFormat> convertable_values[], + const scoped_refptr<ConvertableToTraceFormat>* convertable_values, unsigned char flags) { int thread_id = static_cast<int>(base::PlatformThread::CurrentId()); base::TimeTicks now = base::TimeTicks::NowFromSystemTraceTime(); - AddTraceEventWithThreadIdAndTimestamp(phase, category_group_enabled, name, id, - thread_id, now, num_args, arg_names, - arg_types, arg_values, - convertable_values, flags); + return AddTraceEventWithThreadIdAndTimestamp(phase, category_group_enabled, + name, id, thread_id, now, + num_args, arg_names, + arg_types, arg_values, + convertable_values, flags); } -void TraceLog::AddTraceEventWithThreadIdAndTimestamp( +TraceEventHandle TraceLog::AddTraceEventWithThreadIdAndTimestamp( char phase, const unsigned char* category_group_enabled, const char* name, @@ -1475,35 +1690,41 @@ void TraceLog::AddTraceEventWithThreadIdAndTimestamp( const char** arg_names, const unsigned char* arg_types, const unsigned long long* arg_values, - scoped_ptr<ConvertableToTraceFormat> convertable_values[], + const scoped_refptr<ConvertableToTraceFormat>* convertable_values, unsigned char flags) { + TraceEventHandle handle = { 0, 0, 0 }; + if (!*category_group_enabled) + return handle; + + // Avoid re-entrance of AddTraceEvent. This may happen in GPU process when + // ECHO_TO_CONSOLE is enabled: AddTraceEvent -> LOG(ERROR) -> + // GpuProcessLogMessageHandler -> PostPendingTask -> TRACE_EVENT ... + if (thread_is_in_trace_event_.Get()) + return handle; + + AutoThreadLocalBoolean thread_is_in_trace_event(&thread_is_in_trace_event_); + DCHECK(name); if (flags & TRACE_EVENT_FLAG_MANGLE_ID) id ^= process_id_hash_; -#if defined(OS_ANDROID) - SendToATrace(phase, GetCategoryGroupName(category_group_enabled), name, id, - num_args, arg_names, arg_types, arg_values, convertable_values, - flags); -#endif - - if (!IsCategoryGroupEnabled(category_group_enabled)) - return; - - TimeTicks now = timestamp - time_offset_; + TimeTicks now = OffsetTimestamp(timestamp); TimeTicks thread_now = ThreadNow(); - NotificationHelper notifier(this); - ThreadLocalEventBuffer* thread_local_event_buffer = NULL; // A ThreadLocalEventBuffer needs the message loop // - to know when the thread exits; // - to handle the final flush. - // For a thread without a message loop, the trace events will be added into - // the main buffer directly. - if (MessageLoop::current()) { + // For a thread without a message loop or the message loop may be blocked, the + // trace events will be added into the main buffer directly. + if (!thread_blocks_message_loop_.Get() && MessageLoop::current()) { thread_local_event_buffer = thread_local_event_buffer_.Get(); + if (thread_local_event_buffer && + !CheckGeneration(thread_local_event_buffer->generation())) { + delete thread_local_event_buffer; + thread_local_event_buffer = NULL; + } if (!thread_local_event_buffer) { thread_local_event_buffer = new ThreadLocalEventBuffer(this); thread_local_event_buffer_.Set(thread_local_event_buffer); @@ -1523,7 +1744,8 @@ void TraceLog::AddTraceEventWithThreadIdAndTimestamp( new_name && *new_name) { g_current_thread_name.Get().Set(new_name); - AutoLock lock(lock_); + AutoLock thread_info_lock(thread_info_lock_); + hash_map<int, std::string>::iterator existing_name = thread_names_.find(thread_id); if (existing_name == thread_names_.end()) { @@ -1538,82 +1760,128 @@ void TraceLog::AddTraceEventWithThreadIdAndTimestamp( existing_names.end(), new_name) != existing_names.end(); if (!found) { - existing_name->second.push_back(','); + if (existing_names.size()) + existing_name->second.push_back(','); existing_name->second.append(new_name); } } } } - if (!subtle::NoBarrier_Load(&buffer_is_full_)) { - TraceEvent trace_event(thread_id, now, thread_now, phase, - category_group_enabled, name, id, - num_args, arg_names, arg_types, arg_values, - convertable_values, flags); + std::string console_message; + if ((*category_group_enabled & ENABLED_FOR_RECORDING)) { + OptionalAutoLock lock(lock_); + TraceEvent* trace_event = NULL; if (thread_local_event_buffer) { - thread_local_event_buffer->AddEvent(trace_event, ¬ifier); + trace_event = thread_local_event_buffer->AddTraceEvent(&handle); } else { - AutoLock lock(lock_); - AddEventToMainBufferWhileLocked(trace_event); - CheckIfBufferIsFullWhileLocked(¬ifier); + lock.EnsureAcquired(); + trace_event = AddEventToThreadSharedChunkWhileLocked(&handle, true); } - if (trace_options() & ECHO_TO_CONSOLE) { - AutoLock lock(lock_); - - TimeDelta duration; - if (phase == TRACE_EVENT_PHASE_END) { - duration = timestamp - thread_event_start_times_[thread_id].top(); - thread_event_start_times_[thread_id].pop(); - } - - std::string thread_name = thread_names_[thread_id]; - if (thread_colors_.find(thread_name) == thread_colors_.end()) - thread_colors_[thread_name] = (thread_colors_.size() % 6) + 1; - - std::ostringstream log; - log << base::StringPrintf("%s: \x1b[0;3%dm", - thread_name.c_str(), - thread_colors_[thread_name]); - - size_t depth = 0; - if (thread_event_start_times_.find(thread_id) != - thread_event_start_times_.end()) - depth = thread_event_start_times_[thread_id].size(); + if (trace_event) { + trace_event->Initialize(thread_id, now, thread_now, phase, + category_group_enabled, name, id, + num_args, arg_names, arg_types, arg_values, + convertable_values, flags); - for (size_t i = 0; i < depth; ++i) - log << "| "; - - trace_event.AppendPrettyPrinted(&log); - if (phase == TRACE_EVENT_PHASE_END) - log << base::StringPrintf(" (%.3f ms)", duration.InMillisecondsF()); - - LOG(ERROR) << log.str() << "\x1b[0;m"; +#if defined(OS_ANDROID) + trace_event->SendToATrace(); +#endif + } - if (phase == TRACE_EVENT_PHASE_BEGIN) - thread_event_start_times_[thread_id].push(timestamp); + if (trace_options() & ECHO_TO_CONSOLE) { + console_message = EventToConsoleMessage( + phase == TRACE_EVENT_PHASE_COMPLETE ? TRACE_EVENT_PHASE_BEGIN : phase, + timestamp, trace_event); } } + if (console_message.size()) + LOG(ERROR) << console_message; + if (reinterpret_cast<const unsigned char*>(subtle::NoBarrier_Load( &watch_category_)) == category_group_enabled) { - AutoLock lock(lock_); - if (watch_event_name_ == name) - notifier.AddNotificationWhileLocked(EVENT_WATCH_NOTIFICATION); + bool event_name_matches; + WatchEventCallback watch_event_callback_copy; + { + AutoLock lock(lock_); + event_name_matches = watch_event_name_ == name; + watch_event_callback_copy = watch_event_callback_; + } + if (event_name_matches) { + if (!watch_event_callback_copy.is_null()) + watch_event_callback_copy.Run(); + } } - notifier.SendNotificationIfAny(); - EventCallback event_callback = reinterpret_cast<EventCallback>( - subtle::NoBarrier_Load(&event_callback_)); - if (event_callback) { - event_callback(phase, category_group_enabled, name, id, - num_args, arg_names, arg_types, arg_values, - flags); + if (*category_group_enabled & ENABLED_FOR_EVENT_CALLBACK) { + EventCallback event_callback = reinterpret_cast<EventCallback>( + subtle::NoBarrier_Load(&event_callback_)); + if (event_callback) { + event_callback(now, + phase == TRACE_EVENT_PHASE_COMPLETE ? + TRACE_EVENT_PHASE_BEGIN : phase, + category_group_enabled, name, id, + num_args, arg_names, arg_types, arg_values, + flags); + } } if (thread_local_event_buffer) thread_local_event_buffer->ReportOverhead(now, thread_now); + + return handle; +} + +// May be called when a COMPELETE event ends and the unfinished event has been +// recycled (phase == TRACE_EVENT_PHASE_END and trace_event == NULL). +std::string TraceLog::EventToConsoleMessage(unsigned char phase, + const TimeTicks& timestamp, + TraceEvent* trace_event) { + AutoLock thread_info_lock(thread_info_lock_); + + // The caller should translate TRACE_EVENT_PHASE_COMPLETE to + // TRACE_EVENT_PHASE_BEGIN or TRACE_EVENT_END. + DCHECK(phase != TRACE_EVENT_PHASE_COMPLETE); + + TimeDelta duration; + int thread_id = trace_event ? + trace_event->thread_id() : PlatformThread::CurrentId(); + if (phase == TRACE_EVENT_PHASE_END) { + duration = timestamp - thread_event_start_times_[thread_id].top(); + thread_event_start_times_[thread_id].pop(); + } + + std::string thread_name = thread_names_[thread_id]; + if (thread_colors_.find(thread_name) == thread_colors_.end()) + thread_colors_[thread_name] = (thread_colors_.size() % 6) + 1; + + std::ostringstream log; + log << base::StringPrintf("%s: \x1b[0;3%dm", + thread_name.c_str(), + thread_colors_[thread_name]); + + size_t depth = 0; + if (thread_event_start_times_.find(thread_id) != + thread_event_start_times_.end()) + depth = thread_event_start_times_[thread_id].size(); + + for (size_t i = 0; i < depth; ++i) + log << "| "; + + if (trace_event) + trace_event->AppendPrettyPrinted(&log); + if (phase == TRACE_EVENT_PHASE_END) + log << base::StringPrintf(" (%.3f ms)", duration.InMillisecondsF()); + + log << "\x1b[0;m"; + + if (phase == TRACE_EVENT_PHASE_BEGIN) + thread_event_start_times_[thread_id].push(timestamp); + + return log.str(); } void TraceLog::AddTraceEventEtw(char phase, @@ -1639,77 +1907,89 @@ void TraceLog::AddTraceEventEtw(char phase, TRACE_EVENT_FLAG_COPY, "id", id, "extra", extra); } -void TraceLog::SetWatchEvent(const std::string& category_name, - const std::string& event_name) { - const unsigned char* category = GetCategoryGroupEnabled( - category_name.c_str()); - size_t notify_count = 0; - { - AutoLock lock(lock_); - subtle::NoBarrier_Store(&watch_category_, - reinterpret_cast<subtle::AtomicWord>(category)); - watch_event_name_ = event_name; +void TraceLog::UpdateTraceEventDuration( + const unsigned char* category_group_enabled, + const char* name, + TraceEventHandle handle) { + // Avoid re-entrance of AddTraceEvent. This may happen in GPU process when + // ECHO_TO_CONSOLE is enabled: AddTraceEvent -> LOG(ERROR) -> + // GpuProcessLogMessageHandler -> PostPendingTask -> TRACE_EVENT ... + if (thread_is_in_trace_event_.Get()) + return; - // First, search existing events for watch event because we want to catch - // it even if it has already occurred. - notify_count = logged_events_->CountEnabledByName(category, event_name); - } // release lock + AutoThreadLocalBoolean thread_is_in_trace_event(&thread_is_in_trace_event_); - // Send notification for each event found. - for (size_t i = 0; i < notify_count; ++i) { - NotificationHelper notifier(this); - lock_.Acquire(); - notifier.AddNotificationWhileLocked(EVENT_WATCH_NOTIFICATION); - lock_.Release(); - notifier.SendNotificationIfAny(); + TimeTicks thread_now = ThreadNow(); + TimeTicks now = OffsetNow(); + + std::string console_message; + if (*category_group_enabled & ENABLED_FOR_RECORDING) { + OptionalAutoLock lock(lock_); + + TraceEvent* trace_event = GetEventByHandleInternal(handle, &lock); + if (trace_event) { + DCHECK(trace_event->phase() == TRACE_EVENT_PHASE_COMPLETE); + trace_event->UpdateDuration(now, thread_now); +#if defined(OS_ANDROID) + trace_event->SendToATrace(); +#endif + } + + if (trace_options() & ECHO_TO_CONSOLE) { + console_message = EventToConsoleMessage(TRACE_EVENT_PHASE_END, + now, trace_event); + } } + + if (console_message.size()) + LOG(ERROR) << console_message; + + if (*category_group_enabled & ENABLED_FOR_EVENT_CALLBACK) { + EventCallback event_callback = reinterpret_cast<EventCallback>( + subtle::NoBarrier_Load(&event_callback_)); + if (event_callback) { + event_callback(now, TRACE_EVENT_PHASE_END, category_group_enabled, name, + trace_event_internal::kNoEventId, 0, NULL, NULL, NULL, + TRACE_EVENT_FLAG_NONE); + } + } +} + +void TraceLog::SetWatchEvent(const std::string& category_name, + const std::string& event_name, + const WatchEventCallback& callback) { + const unsigned char* category = GetCategoryGroupEnabled( + category_name.c_str()); + AutoLock lock(lock_); + subtle::NoBarrier_Store(&watch_category_, + reinterpret_cast<subtle::AtomicWord>(category)); + watch_event_name_ = event_name; + watch_event_callback_ = callback; } void TraceLog::CancelWatchEvent() { AutoLock lock(lock_); subtle::NoBarrier_Store(&watch_category_, 0); watch_event_name_ = ""; + watch_event_callback_.Reset(); } -namespace { - -template <typename T> -void AddMetadataEventToBuffer( - TraceBuffer* logged_events, - int thread_id, - const char* metadata_name, const char* arg_name, - const T& value) { - int num_args = 1; - unsigned char arg_type; - unsigned long long arg_value; - trace_event_internal::SetTraceValue(value, &arg_type, &arg_value); - logged_events->AddEvent(TraceEvent( - thread_id, - TimeTicks(), TimeTicks(), TRACE_EVENT_PHASE_METADATA, - &g_category_group_enabled[g_category_metadata], - metadata_name, trace_event_internal::kNoEventId, - num_args, &arg_name, &arg_type, &arg_value, NULL, - TRACE_EVENT_FLAG_NONE)); -} - -} - -void TraceLog::AddMetadataEvents() { +void TraceLog::AddMetadataEventsWhileLocked() { lock_.AssertAcquired(); int current_thread_id = static_cast<int>(base::PlatformThread::CurrentId()); if (process_sort_index_ != 0) { - AddMetadataEventToBuffer(logged_events_.get(), - current_thread_id, - "process_sort_index", "sort_index", - process_sort_index_); + InitializeMetadataEvent(AddEventToThreadSharedChunkWhileLocked(NULL, false), + current_thread_id, + "process_sort_index", "sort_index", + process_sort_index_); } if (process_name_.size()) { - AddMetadataEventToBuffer(logged_events_.get(), - current_thread_id, - "process_name", "name", - process_name_); + InitializeMetadataEvent(AddEventToThreadSharedChunkWhileLocked(NULL, false), + current_thread_id, + "process_name", "name", + process_name_); } if (process_labels_.size() > 0) { @@ -1719,10 +1999,10 @@ void TraceLog::AddMetadataEvents() { it++) { labels.push_back(it->second); } - AddMetadataEventToBuffer(logged_events_.get(), - current_thread_id, - "process_labels", "labels", - JoinString(labels, ',')); + InitializeMetadataEvent(AddEventToThreadSharedChunkWhileLocked(NULL, false), + current_thread_id, + "process_labels", "labels", + JoinString(labels, ',')); } // Thread sort indices. @@ -1731,34 +2011,66 @@ void TraceLog::AddMetadataEvents() { it++) { if (it->second == 0) continue; - AddMetadataEventToBuffer(logged_events_.get(), - it->first, - "thread_sort_index", "sort_index", - it->second); + InitializeMetadataEvent(AddEventToThreadSharedChunkWhileLocked(NULL, false), + it->first, + "thread_sort_index", "sort_index", + it->second); } // Thread names. + AutoLock thread_info_lock(thread_info_lock_); for(hash_map<int, std::string>::iterator it = thread_names_.begin(); it != thread_names_.end(); it++) { if (it->second.empty()) continue; - AddMetadataEventToBuffer(logged_events_.get(), - it->first, - "thread_name", "name", - it->second); + InitializeMetadataEvent(AddEventToThreadSharedChunkWhileLocked(NULL, false), + it->first, + "thread_name", "name", + it->second); } } -void TraceLog::InstallWaitableEventForSamplingTesting( - WaitableEvent* waitable_event) { - sampling_thread_->InstallWaitableEventForSamplingTesting(waitable_event); +void TraceLog::WaitSamplingEventForTesting() { + if (!sampling_thread_) + return; + sampling_thread_->WaitSamplingEventForTesting(); } void TraceLog::DeleteForTesting() { DeleteTraceLogForTesting::Delete(); } +TraceEvent* TraceLog::GetEventByHandle(TraceEventHandle handle) { + return GetEventByHandleInternal(handle, NULL); +} + +TraceEvent* TraceLog::GetEventByHandleInternal(TraceEventHandle handle, + OptionalAutoLock* lock) { + if (!handle.chunk_seq) + return NULL; + + if (thread_local_event_buffer_.Get()) { + TraceEvent* trace_event = + thread_local_event_buffer_.Get()->GetEventByHandle(handle); + if (trace_event) + return trace_event; + } + + // The event has been out-of-control of the thread local buffer. + // Try to get the event from the main buffer with a lock. + if (lock) + lock->EnsureAcquired(); + + if (thread_shared_chunk_ && + handle.chunk_index == thread_shared_chunk_index_) { + return handle.chunk_seq == thread_shared_chunk_->seq() ? + thread_shared_chunk_->GetEventAt(handle.event_index) : NULL; + } + + return logged_events_->GetEventByHandle(handle); +} + void TraceLog::SetProcessID(int process_id) { process_id_ = process_id; // Create a FNV hash from the process ID for XORing. @@ -1791,7 +2103,7 @@ void TraceLog::UpdateProcessLabel( void TraceLog::RemoveProcessLabel(int label_id) { AutoLock lock(lock_); base::hash_map<int, std::string>::iterator it = process_labels_.find( - label_id); + label_id); if (it == process_labels_.end()) return; @@ -1811,6 +2123,14 @@ size_t TraceLog::GetObserverCountForTest() const { return enabled_state_observer_list_.size(); } +void TraceLog::SetCurrentThreadBlocksMessageLoop() { + thread_blocks_message_loop_.Set(true); + if (thread_local_event_buffer_.Get()) { + // This will flush the thread local buffer. + delete thread_local_event_buffer_.Get(); + } +} + bool CategoryFilter::IsEmptyOrContainsLeadingOrTrailingWhitespace( const std::string& str) { return str.empty() || @@ -1972,49 +2292,29 @@ void CategoryFilter::Clear() { namespace trace_event_internal { -ScopedTrace::ScopedTrace( - TRACE_EVENT_API_ATOMIC_WORD* event_uid, const char* name) { - category_group_enabled_ = - reinterpret_cast<const unsigned char*>(TRACE_EVENT_API_ATOMIC_LOAD( - *event_uid)); - if (!category_group_enabled_) { - category_group_enabled_ = TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED("gpu"); - TRACE_EVENT_API_ATOMIC_STORE( - *event_uid, - reinterpret_cast<TRACE_EVENT_API_ATOMIC_WORD>(category_group_enabled_)); +ScopedTraceBinaryEfficient::ScopedTraceBinaryEfficient( + const char* category_group, const char* name) { + // The single atom works because for now the category_group can only be "gpu". + DCHECK(strcmp(category_group, "gpu") == 0); + static TRACE_EVENT_API_ATOMIC_WORD atomic = 0; + INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO_CUSTOM_VARIABLES( + category_group, atomic, category_group_enabled_); + name_ = name; + if (*category_group_enabled_) { + event_handle_ = + TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_THREAD_ID_AND_TIMESTAMP( + TRACE_EVENT_PHASE_COMPLETE, category_group_enabled_, name, + trace_event_internal::kNoEventId, + static_cast<int>(base::PlatformThread::CurrentId()), + base::TimeTicks::NowFromSystemTraceTime(), + 0, NULL, NULL, NULL, NULL, TRACE_EVENT_FLAG_NONE); } +} + +ScopedTraceBinaryEfficient::~ScopedTraceBinaryEfficient() { if (*category_group_enabled_) { - name_ = name; - TRACE_EVENT_API_ADD_TRACE_EVENT( - TRACE_EVENT_PHASE_BEGIN, // phase - category_group_enabled_, // category enabled - name, // name - 0, // id - 0, // num_args - NULL, // arg_names - NULL, // arg_types - NULL, // arg_values - NULL, // convertable_values - TRACE_EVENT_FLAG_NONE); // flags - } else { - category_group_enabled_ = NULL; - name_ = NULL; - } -} - -ScopedTrace::~ScopedTrace() { - if (category_group_enabled_ && *category_group_enabled_) { - TRACE_EVENT_API_ADD_TRACE_EVENT( - TRACE_EVENT_PHASE_END, // phase - category_group_enabled_, // category enabled - name_, // name - 0, // id - 0, // num_args - NULL, // arg_names - NULL, // arg_types - NULL, // arg_values - NULL, // convertable values - TRACE_EVENT_FLAG_NONE); // flags + TRACE_EVENT_API_UPDATE_TRACE_EVENT_DURATION(category_group_enabled_, + name_, event_handle_); } } diff --git a/chromium/base/debug/trace_event_impl.h b/chromium/base/debug/trace_event_impl.h index 4967c10ead5..61794807a41 100644 --- a/chromium/base/debug/trace_event_impl.h +++ b/chromium/base/debug/trace_event_impl.h @@ -64,23 +64,29 @@ namespace debug { // For any argument of type TRACE_VALUE_TYPE_CONVERTABLE the provided // class must implement this interface. -class ConvertableToTraceFormat { +class ConvertableToTraceFormat : public RefCounted<ConvertableToTraceFormat> { public: - virtual ~ConvertableToTraceFormat() {} - // Append the class info to the provided |out| string. The appended // data must be a valid JSON object. Strings must be properly quoted, and // escaped. There is no processing applied to the content after it is // appended. virtual void AppendAsTraceFormat(std::string* out) const = 0; + + protected: + virtual ~ConvertableToTraceFormat() {} + + private: + friend class RefCounted<ConvertableToTraceFormat>; +}; + +struct TraceEventHandle { + uint32 chunk_seq; + uint16 chunk_index; + uint16 event_index; }; const int kTraceMaxNumArgs = 2; -// Output records are "Events" and can be obtained via the -// OutputCallback whenever the tracing system decides to flush. This -// can happen at any time, on any thread, or you can programmatically -// force it to happen. class BASE_EXPORT TraceEvent { public: union TraceValue { @@ -93,23 +99,31 @@ class BASE_EXPORT TraceEvent { }; TraceEvent(); - TraceEvent(int thread_id, - TimeTicks timestamp, - TimeTicks thread_timestamp, - char phase, - const unsigned char* category_group_enabled, - const char* name, - unsigned long long id, - int num_args, - const char** arg_names, - const unsigned char* arg_types, - const unsigned long long* arg_values, - scoped_ptr<ConvertableToTraceFormat> convertable_values[], - unsigned char flags); - TraceEvent(const TraceEvent& other); - TraceEvent& operator=(const TraceEvent& other); ~TraceEvent(); + // We don't need to copy TraceEvent except when TraceEventBuffer is cloned. + // Use explicit copy method to avoid accidentally misuse of copy. + void CopyFrom(const TraceEvent& other); + + void Initialize( + int thread_id, + TimeTicks timestamp, + TimeTicks thread_timestamp, + char phase, + const unsigned char* category_group_enabled, + const char* name, + unsigned long long id, + int num_args, + const char** arg_names, + const unsigned char* arg_types, + const unsigned long long* arg_values, + const scoped_refptr<ConvertableToTraceFormat>* convertable_values, + unsigned char flags); + + void Reset(); + + void UpdateDuration(const TimeTicks& now, const TimeTicks& thread_now); + // Serialize event data to JSON static void AppendEventsAsJSON(const std::vector<TraceEvent>& events, size_t start, @@ -126,6 +140,10 @@ class BASE_EXPORT TraceEvent { TimeTicks thread_timestamp() const { return thread_timestamp_; } char phase() const { return phase_; } int thread_id() const { return thread_id_; } + TimeDelta duration() const { return duration_; } + TimeDelta thread_duration() const { return thread_duration_; } + unsigned long long id() const { return id_; } + unsigned char flags() const { return flags_; } // Exposed for unittesting: @@ -139,15 +157,21 @@ class BASE_EXPORT TraceEvent { const char* name() const { return name_; } +#if defined(OS_ANDROID) + void SendToATrace(); +#endif + private: // Note: these are ordered by size (largest first) for optimal packing. TimeTicks timestamp_; TimeTicks thread_timestamp_; + TimeDelta duration_; + TimeDelta thread_duration_; // id_ can be used to store phase-specific data. unsigned long long id_; TraceValue arg_values_[kTraceMaxNumArgs]; const char* arg_names_[kTraceMaxNumArgs]; - scoped_ptr<ConvertableToTraceFormat> convertable_values_[kTraceMaxNumArgs]; + scoped_refptr<ConvertableToTraceFormat> convertable_values_[kTraceMaxNumArgs]; const unsigned char* category_group_enabled_; const char* name_; scoped_refptr<base::RefCountedString> parameter_copy_storage_; @@ -155,6 +179,43 @@ class BASE_EXPORT TraceEvent { char phase_; unsigned char flags_; unsigned char arg_types_[kTraceMaxNumArgs]; + + DISALLOW_COPY_AND_ASSIGN(TraceEvent); +}; + +// TraceBufferChunk is the basic unit of TraceBuffer. +class BASE_EXPORT TraceBufferChunk { + public: + TraceBufferChunk(uint32 seq) + : next_free_(0), + seq_(seq) { + } + + void Reset(uint32 new_seq); + TraceEvent* AddTraceEvent(size_t* event_index); + bool IsFull() const { return next_free_ == kTraceBufferChunkSize; } + + uint32 seq() const { return seq_; } + size_t capacity() const { return kTraceBufferChunkSize; } + size_t size() const { return next_free_; } + + TraceEvent* GetEventAt(size_t index) { + DCHECK(index < size()); + return &chunk_[index]; + } + const TraceEvent* GetEventAt(size_t index) const { + DCHECK(index < size()); + return &chunk_[index]; + } + + scoped_ptr<TraceBufferChunk> Clone() const; + + static const size_t kTraceBufferChunkSize = 64; + + private: + size_t next_free_; + TraceEvent chunk_[kTraceBufferChunkSize]; + uint32 seq_; }; // TraceBuffer holds the events as they are collected. @@ -162,15 +223,19 @@ class BASE_EXPORT TraceBuffer { public: virtual ~TraceBuffer() {} - virtual void AddEvent(const TraceEvent& event) = 0; - virtual bool HasMoreEvents() const = 0; - virtual const TraceEvent& NextEvent() = 0; + virtual scoped_ptr<TraceBufferChunk> GetChunk(size_t *index) = 0; + virtual void ReturnChunk(size_t index, + scoped_ptr<TraceBufferChunk> chunk) = 0; + virtual bool IsFull() const = 0; - virtual size_t CountEnabledByName(const unsigned char* category, - const std::string& event_name) const = 0; virtual size_t Size() const = 0; virtual size_t Capacity() const = 0; - virtual const TraceEvent& GetEventAt(size_t index) const = 0; + virtual TraceEvent* GetEventByHandle(TraceEventHandle handle) = 0; + + // For iteration. Each TraceBuffer can only be iterated once. + virtual const TraceBufferChunk* NextChunk() = 0; + + virtual scoped_ptr<TraceBuffer> CloneForIteration() const = 0; }; // TraceResultBuffer collects and converts trace fragments returned by TraceLog @@ -289,16 +354,6 @@ class TraceSamplingThread; class BASE_EXPORT TraceLog { public: - // Notification is a mask of one or more of the following events. - enum Notification { - // The trace buffer does not flush dynamically, so when it fills up, - // subsequent trace events will be dropped. This callback is generated when - // the trace buffer is full. The callback must be thread safe. - TRACE_BUFFER_FULL = 1 << 0, - // A subscribed trace-event occurred. - EVENT_WATCH_NOTIFICATION = 1 << 1 - }; - // Options determines how the trace buffer stores data. enum Options { // Record until the trace buffer is full. @@ -308,37 +363,49 @@ class BASE_EXPORT TraceLog { // and we use it as a ring buffer during recording. RECORD_CONTINUOUSLY = 1 << 1, - // Enable the sampling profiler. + // Enable the sampling profiler in the recording mode. ENABLE_SAMPLING = 1 << 2, + // Enable the sampling profiler in the monitoring mode. + MONITOR_SAMPLING = 1 << 3, + // Echo to console. Events are discarded. - ECHO_TO_CONSOLE = 1 << 3, + ECHO_TO_CONSOLE = 1 << 4, }; - static TraceLog* GetInstance(); + // The pointer returned from GetCategoryGroupEnabledInternal() points to a + // value with zero or more of the following bits. Used in this class only. + // The TRACE_EVENT macros should only use the value as a bool. + enum CategoryGroupEnabledFlags { + // Normal enabled flag for category groups enabled by SetEnabled(). + ENABLED_FOR_RECORDING = 1 << 0, + // Category group enabled by SetEventCallbackEnabled(). + ENABLED_FOR_EVENT_CALLBACK = 1 << 1, + }; - // Convert the given string to trace options. Defaults to RECORD_UNTIL_FULL if - // the string does not provide valid options. - static Options TraceOptionsFromString(const std::string& str); + static TraceLog* GetInstance(); // Get set of known category groups. This can change as new code paths are // reached. The known category groups are inserted into |category_groups|. void GetKnownCategoryGroups(std::vector<std::string>* category_groups); - // Retrieves the current CategoryFilter. - const CategoryFilter& GetCurrentCategoryFilter(); + // Retrieves a copy (for thread-safety) of the current CategoryFilter. + CategoryFilter GetCurrentCategoryFilter(); Options trace_options() const { return static_cast<Options>(subtle::NoBarrier_Load(&trace_options_)); } - // Enables tracing. See CategoryFilter comments for details - // on how to control what categories will be traced. + // Enables normal tracing (recording trace events in the trace buffer). + // See CategoryFilter comments for details on how to control what categories + // will be traced. If tracing has already been enabled, |category_filter| will + // be merged into the current category filter. void SetEnabled(const CategoryFilter& category_filter, Options options); - // Disable tracing for all categories. + // Disables normal tracing for all categories. void SetDisabled(); - bool IsEnabled() { return !!enable_count_; } + + bool IsEnabled() { return enabled_; } // The number of times we have begun recording traces. If tracing is off, // returns -1. If tracing is on, then it returns the number of times we have @@ -350,6 +417,7 @@ class BASE_EXPORT TraceLog { #if defined(OS_ANDROID) void StartATrace(); void StopATrace(); + void AddClockSyncMetadataEvent(); #endif // Enabled state listeners give a callback when tracing is enabled or @@ -370,21 +438,19 @@ class BASE_EXPORT TraceLog { bool HasEnabledStateObserver(EnabledStateObserver* listener) const; float GetBufferPercentFull() const; - - // Set the thread-safe notification callback. The callback can occur at any - // time and from any thread. WARNING: It is possible for the previously set - // callback to be called during OR AFTER a call to SetNotificationCallback. - // Therefore, the target of the callback must either be a global function, - // ref-counted object or a LazyInstance with Leaky traits (or equivalent). - typedef base::Callback<void(int)> NotificationCallback; - void SetNotificationCallback(const NotificationCallback& cb); + bool BufferIsFull() const; // Not using base::Callback because of its limited by 7 parameters. // Also, using primitive type allows directly passing callback from WebCore. // WARNING: It is possible for the previously set callback to be called - // after a call to SetEventCallback() that replaces or clears the callback. + // after a call to SetEventCallbackEnabled() that replaces or a call to + // SetEventCallbackDisabled() that disables the callback. // This callback may be invoked on any thread. - typedef void (*EventCallback)(char phase, + // For TRACE_EVENT_PHASE_COMPLETE events, the client will still receive pairs + // of TRACE_EVENT_PHASE_BEGIN and TRACE_EVENT_PHASE_END events to keep the + // interface simple. + typedef void (*EventCallback)(TimeTicks timestamp, + char phase, const unsigned char* category_group_enabled, const char* name, unsigned long long id, @@ -393,7 +459,11 @@ class BASE_EXPORT TraceLog { const unsigned char arg_types[], const unsigned long long arg_values[], unsigned char flags); - void SetEventCallback(EventCallback cb); + + // Enable tracing for EventCallback. + void SetEventCallbackEnabled(const CategoryFilter& category_filter, + EventCallback cb); + void SetEventCallbackDisabled(); // Flush all collected events to the given output callback. The callback will // be called one or more times either synchronously or asynchronously from @@ -407,6 +477,7 @@ class BASE_EXPORT TraceLog { typedef base::Callback<void(const scoped_refptr<base::RefCountedString>&, bool has_more_events)> OutputCallback; void Flush(const OutputCallback& cb); + void FlushButLeaveBufferIntact(const OutputCallback& flush_output_callback); // Called by TRACE_EVENT* macros, don't call this directly. // The name parameter is a category group for example: @@ -418,17 +489,18 @@ class BASE_EXPORT TraceLog { // Called by TRACE_EVENT* macros, don't call this directly. // If |copy| is set, |name|, |arg_name1| and |arg_name2| will be deep copied // into the event; see "Memory scoping note" and TRACE_EVENT_COPY_XXX above. - void AddTraceEvent(char phase, - const unsigned char* category_group_enabled, - const char* name, - unsigned long long id, - int num_args, - const char** arg_names, - const unsigned char* arg_types, - const unsigned long long* arg_values, - scoped_ptr<ConvertableToTraceFormat> convertable_values[], - unsigned char flags); - void AddTraceEventWithThreadIdAndTimestamp( + TraceEventHandle AddTraceEvent( + char phase, + const unsigned char* category_group_enabled, + const char* name, + unsigned long long id, + int num_args, + const char** arg_names, + const unsigned char* arg_types, + const unsigned long long* arg_values, + const scoped_refptr<ConvertableToTraceFormat>* convertable_values, + unsigned char flags); + TraceEventHandle AddTraceEventWithThreadIdAndTimestamp( char phase, const unsigned char* category_group_enabled, const char* name, @@ -439,7 +511,7 @@ class BASE_EXPORT TraceLog { const char** arg_names, const unsigned char* arg_types, const unsigned long long* arg_values, - scoped_ptr<ConvertableToTraceFormat> convertable_values[], + const scoped_refptr<ConvertableToTraceFormat>* convertable_values, unsigned char flags); static void AddTraceEventEtw(char phase, const char* category_group, @@ -450,12 +522,15 @@ class BASE_EXPORT TraceLog { const void* id, const std::string& extra); - // For every matching event, a notification will be fired. NOTE: the - // notification will fire for each matching event that has already occurred - // since tracing was started (including before tracing if the process was - // started with tracing turned on). + void UpdateTraceEventDuration(const unsigned char* category_group_enabled, + const char* name, + TraceEventHandle handle); + + // For every matching event, the callback will be called. + typedef base::Callback<void()> WatchEventCallback; void SetWatchEvent(const std::string& category_name, - const std::string& event_name); + const std::string& event_name, + const WatchEventCallback& callback); // Cancel the watch event. If tracing is enabled, this may race with the // watch event notification firing. void CancelWatchEvent(); @@ -464,16 +539,14 @@ class BASE_EXPORT TraceLog { // Exposed for unittesting: - void InstallWaitableEventForSamplingTesting(WaitableEvent* waitable_event); + void WaitSamplingEventForTesting(); // Allows deleting our singleton instance. static void DeleteForTesting(); // Allow tests to inspect TraceEvents. size_t GetEventsSize() const { return logged_events_->Size(); } - const TraceEvent& GetEventAt(size_t index) const { - return logged_events_->GetEventAt(index); - } + TraceEvent* GetEventByHandle(TraceEventHandle handle); void SetProcessID(int process_id); @@ -501,95 +574,86 @@ class BASE_EXPORT TraceLog { size_t GetObserverCountForTest() const; + // Call this method if the current thread may block the message loop to + // prevent the thread from using the thread-local buffer because the thread + // may not handle the flush request in time causing lost of unflushed events. + void SetCurrentThreadBlocksMessageLoop(); + private: + FRIEND_TEST_ALL_PREFIXES(TraceEventTestFixture, + TraceBufferRingBufferGetReturnChunk); + FRIEND_TEST_ALL_PREFIXES(TraceEventTestFixture, + TraceBufferRingBufferHalfIteration); + FRIEND_TEST_ALL_PREFIXES(TraceEventTestFixture, + TraceBufferRingBufferFullIteration); + // This allows constructor and destructor to be private and usable only // by the Singleton class. friend struct DefaultSingletonTraits<TraceLog>; - // Enable/disable each category group based on the current enable_count_ - // and category_filter_. Disable the category group if enabled_count_ is 0, or - // if the category group contains a category that matches an included category - // pattern, that category group will be enabled. - // On Android, ATRACE_ENABLED flag will be applied if atrace is started. + // Enable/disable each category group based on the current enabled_, + // category_filter_, event_callback_ and event_callback_category_filter_. + // Enable the category group if enabled_ is true and category_filter_ matches + // the category group, or event_callback_ is not null and + // event_callback_category_filter_ matches the category group. void UpdateCategoryGroupEnabledFlags(); void UpdateCategoryGroupEnabledFlag(int category_index); - static void SetCategoryGroupEnabled(int category_index, bool enabled); - static bool IsCategoryGroupEnabled( - const unsigned char* category_group_enabled); - - // The pointer returned from GetCategoryGroupEnabledInternal() points to a - // value with zero or more of the following bits. Used in this class only. - // The TRACE_EVENT macros should only use the value as a bool. - enum CategoryGroupEnabledFlags { - // Normal enabled flag for category groups enabled with Enable(). - CATEGORY_GROUP_ENABLED = 1 << 0, - // On Android if ATrace is enabled, all categories will have this bit. - // Not used on other platforms. - ATRACE_ENABLED = 1 << 1 - }; - - // Helper class for managing notification_thread_count_ and running - // notification callbacks. This is very similar to a reader-writer lock, but - // shares the lock with TraceLog and manages the notification flags. - class NotificationHelper { - public: - inline explicit NotificationHelper(TraceLog* trace_log); - inline ~NotificationHelper(); - - // Called only while TraceLog::lock_ is held. This ORs the given - // notification with any existing notifications. - inline void AddNotificationWhileLocked(int notification); - - // Called only while TraceLog::lock_ is NOT held. If there are any pending - // notifications from previous calls to AddNotificationWhileLocked, this - // will call the NotificationCallback. - inline void SendNotificationIfAny(); - - private: - TraceLog* trace_log_; - NotificationCallback callback_copy_; - int notification_; - }; - class ThreadLocalEventBuffer; + class OptionalAutoLock; TraceLog(); ~TraceLog(); const unsigned char* GetCategoryGroupEnabledInternal(const char* name); - void AddMetadataEvents(); + void AddMetadataEventsWhileLocked(); -#if defined(OS_ANDROID) - void SendToATrace(char phase, - const char* category_group, - const char* name, - unsigned long long id, - int num_args, - const char** arg_names, - const unsigned char* arg_types, - const unsigned long long* arg_values, - scoped_ptr<ConvertableToTraceFormat> convertable_values[], - unsigned char flags); - static void ApplyATraceEnabledFlag(unsigned char* category_group_enabled); -#endif + TraceBuffer* trace_buffer() const { return logged_events_.get(); } + TraceBuffer* CreateTraceBuffer(); + + std::string EventToConsoleMessage(unsigned char phase, + const TimeTicks& timestamp, + TraceEvent* trace_event); - TraceBuffer* GetTraceBuffer(); + TraceEvent* AddEventToThreadSharedChunkWhileLocked(TraceEventHandle* handle, + bool check_buffer_is_full); + void CheckIfBufferIsFullWhileLocked(); + void SetDisabledWhileLocked(); - void AddEventToMainBufferWhileLocked(const TraceEvent& trace_event); - void CheckIfBufferIsFullWhileLocked(NotificationHelper* notifier); - // |flush_count| is used in the following callbacks to check if the callback - // is called for the current flush. - void FlushCurrentThread(int flush_count); - void FinishFlush(int flush_count); - void OnFlushTimeout(int flush_count); + TraceEvent* GetEventByHandleInternal(TraceEventHandle handle, + OptionalAutoLock* lock); - // This lock protects TraceLog member accesses from arbitrary threads. - Lock lock_; + // |generation| is used in the following callbacks to check if the callback + // is called for the flush of the current |logged_events_|. + void FlushCurrentThread(int generation); + void ConvertTraceEventsToTraceFormat(scoped_ptr<TraceBuffer> logged_events, + const TraceLog::OutputCallback& flush_output_callback); + void FinishFlush(int generation); + void OnFlushTimeout(int generation); + + int generation() const { + return static_cast<int>(subtle::NoBarrier_Load(&generation_)); + } + bool CheckGeneration(int generation) const { + return generation == this->generation(); + } + void UseNextTraceBuffer(); + + TimeTicks OffsetNow() const { + return OffsetTimestamp(TimeTicks::NowFromSystemTraceTime()); + } + TimeTicks OffsetTimestamp(const TimeTicks& timestamp) const { + return timestamp - time_offset_; + } + + // This lock protects TraceLog member accesses (except for members protected + // by thread_info_lock_) from arbitrary threads. + mutable Lock lock_; + // This lock protects accesses to thread_names_, thread_event_start_times_ + // and thread_colors_. + Lock thread_info_lock_; int locked_line_; - int enable_count_; + bool enabled_; int num_traces_recorded_; - subtle::AtomicWord /* bool */ buffer_is_full_; - NotificationCallback notification_callback_; scoped_ptr<TraceBuffer> logged_events_; subtle::AtomicWord /* EventCallback */ event_callback_; bool dispatching_to_observer_list_; @@ -613,6 +677,7 @@ class BASE_EXPORT TraceLog { TimeDelta time_offset_; // Allow tests to wake up when certain events occur. + WatchEventCallback watch_event_callback_; subtle::AtomicWord /* const unsigned char* */ watch_category_; std::string watch_event_name_; @@ -623,18 +688,26 @@ class BASE_EXPORT TraceLog { PlatformThreadHandle sampling_thread_handle_; CategoryFilter category_filter_; + CategoryFilter event_callback_category_filter_; ThreadLocalPointer<ThreadLocalEventBuffer> thread_local_event_buffer_; + ThreadLocalBoolean thread_blocks_message_loop_; + ThreadLocalBoolean thread_is_in_trace_event_; // Contains the message loops of threads that have had at least one event // added into the local event buffer. Not using MessageLoopProxy because we // need to know the life time of the message loops. - base::hash_set<MessageLoop*> thread_message_loops_; + hash_set<MessageLoop*> thread_message_loops_; + + // For events which can't be added into the thread local buffer, e.g. events + // from threads without a message loop. + scoped_ptr<TraceBufferChunk> thread_shared_chunk_; + size_t thread_shared_chunk_index_; // Set when asynchronous Flush is in progress. OutputCallback flush_output_callback_; scoped_refptr<MessageLoopProxy> flush_message_loop_proxy_; - int flush_count_; + subtle::AtomicWord generation_; DISALLOW_COPY_AND_ASSIGN(TraceLog); }; diff --git a/chromium/base/debug/trace_event_memory.cc b/chromium/base/debug/trace_event_memory.cc index 42655b310e5..4b2b050df8e 100644 --- a/chromium/base/debug/trace_event_memory.cc +++ b/chromium/base/debug/trace_event_memory.cc @@ -31,7 +31,6 @@ class MemoryDumpHolder : public base::debug::ConvertableToTraceFormat { // Takes ownership of dump, which must be a JSON string, allocated with // malloc() and NULL terminated. explicit MemoryDumpHolder(char* dump) : dump_(dump) {} - virtual ~MemoryDumpHolder() { free(dump_); } // base::debug::ConvertableToTraceFormat overrides: virtual void AppendAsTraceFormat(std::string* out) const OVERRIDE { @@ -39,6 +38,8 @@ class MemoryDumpHolder : public base::debug::ConvertableToTraceFormat { } private: + virtual ~MemoryDumpHolder() { free(dump_); } + char* dump_; DISALLOW_COPY_AND_ASSIGN(MemoryDumpHolder); @@ -216,13 +217,12 @@ void TraceMemoryController::DumpMemoryProfile() { // MemoryDumpHolder takes ownership of this string. See GetHeapProfile() in // tcmalloc for details. char* dump = get_heap_profile_function_(); - scoped_ptr<MemoryDumpHolder> dump_holder(new MemoryDumpHolder(dump)); const int kSnapshotId = 1; TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID( TRACE_DISABLED_BY_DEFAULT("memory"), "memory::Heap", kSnapshotId, - dump_holder.PassAs<base::debug::ConvertableToTraceFormat>()); + scoped_refptr<ConvertableToTraceFormat>(new MemoryDumpHolder(dump))); } void TraceMemoryController::StopProfiling() { @@ -246,11 +246,8 @@ bool TraceMemoryController::IsTimerRunningForTest() const { // static bool ScopedTraceMemory::enabled_ = false; -ScopedTraceMemory::ScopedTraceMemory(const char* category, const char* name) { - // Not enabled indicates that the trace system isn't running, so don't - // record anything. - if (!enabled_) - return; +void ScopedTraceMemory::Initialize(const char* category, const char* name) { + DCHECK(enabled_); // Get our thread's copy of the stack. TraceMemoryStack* trace_memory_stack = GetTraceMemoryStack(); const size_t index = trace_memory_stack->scope_depth; @@ -264,11 +261,8 @@ ScopedTraceMemory::ScopedTraceMemory(const char* category, const char* name) { trace_memory_stack->scope_depth++; } -ScopedTraceMemory::~ScopedTraceMemory() { - // Not enabled indicates that the trace system isn't running, so don't - // record anything. - if (!enabled_) - return; +void ScopedTraceMemory::Destroy() { + DCHECK(enabled_); // Get our thread's copy of the stack. TraceMemoryStack* trace_memory_stack = GetTraceMemoryStack(); // The tracing system can be turned on with ScopedTraceMemory objects diff --git a/chromium/base/debug/trace_event_memory.h b/chromium/base/debug/trace_event_memory.h index 5427154039a..df2e6638266 100644 --- a/chromium/base/debug/trace_event_memory.h +++ b/chromium/base/debug/trace_event_memory.h @@ -94,8 +94,16 @@ class BASE_EXPORT ScopedTraceMemory { // Memory for |category| and |name| must be static, for example, literal // strings in a TRACE_EVENT macro. - ScopedTraceMemory(const char* category, const char* name); - ~ScopedTraceMemory(); + ScopedTraceMemory(const char* category, const char* name) { + if (!enabled_) + return; + Initialize(category, name); + } + ~ScopedTraceMemory() { + if (!enabled_) + return; + Destroy(); + } // Enables the storing of trace names on a per-thread stack. static void set_enabled(bool enabled) { enabled_ = enabled; } @@ -107,6 +115,9 @@ class BASE_EXPORT ScopedTraceMemory { static ScopeData GetScopeDataForTest(int stack_index); private: + void Initialize(const char* category, const char* name); + void Destroy(); + static bool enabled_; DISALLOW_COPY_AND_ASSIGN(ScopedTraceMemory); }; diff --git a/chromium/base/debug/trace_event_system_stats_monitor.cc b/chromium/base/debug/trace_event_system_stats_monitor.cc index b95e712b6af..a7128386d60 100644 --- a/chromium/base/debug/trace_event_system_stats_monitor.cc +++ b/chromium/base/debug/trace_event_system_stats_monitor.cc @@ -25,7 +25,6 @@ namespace { class SystemStatsHolder : public base::debug::ConvertableToTraceFormat { public: SystemStatsHolder() { } - virtual ~SystemStatsHolder() { } // Fills system_metrics_ with profiled system memory and disk stats. // Uses the previous stats to compute rates if this is not the first profile. @@ -37,6 +36,8 @@ class SystemStatsHolder : public base::debug::ConvertableToTraceFormat { } private: + virtual ~SystemStatsHolder() { } + SystemMetrics system_stats_; DISALLOW_COPY_AND_ASSIGN(SystemStatsHolder); @@ -103,14 +104,14 @@ void TraceEventSystemStatsMonitor::StartProfiling() { // If system tracing is enabled, dumps a profile to the tracing system. void TraceEventSystemStatsMonitor::DumpSystemStats() { - scoped_ptr<SystemStatsHolder> dump_holder(new SystemStatsHolder()); + scoped_refptr<SystemStatsHolder> dump_holder = new SystemStatsHolder(); dump_holder->GetSystemProfilingStats(); TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID( TRACE_DISABLED_BY_DEFAULT("system_stats"), "base::TraceEventSystemStatsMonitor::SystemStats", this, - dump_holder.PassAs<base::debug::ConvertableToTraceFormat>()); + scoped_refptr<ConvertableToTraceFormat>(dump_holder)); } void TraceEventSystemStatsMonitor::StopProfiling() { diff --git a/chromium/base/debug/trace_event_unittest.cc b/chromium/base/debug/trace_event_unittest.cc index e7883d02132..aef2c43b6fa 100644 --- a/chromium/base/debug/trace_event_unittest.cc +++ b/chromium/base/debug/trace_event_unittest.cc @@ -53,10 +53,8 @@ class TraceEventTestFixture : public testing::Test { WaitableEvent* flush_complete_event, const scoped_refptr<base::RefCountedString>& events_str, bool has_more_events); - void OnTraceNotification(int notification) { - if (notification & TraceLog::EVENT_WATCH_NOTIFICATION) - ++event_watch_notification_; - notifications_received_ |= notification; + void OnWatchEventMatched() { + ++event_watch_notification_; } DictionaryValue* FindMatchingTraceEntry(const JsonKeyValue* key_values); DictionaryValue* FindNamePhase(const char* name, const char* phase); @@ -79,7 +77,6 @@ class TraceEventTestFixture : public testing::Test { void BeginSpecificTrace(const std::string& filter) { event_watch_notification_ = 0; - notifications_received_ = 0; TraceLog::GetInstance()->SetEnabled(CategoryFilter(filter), TraceLog::RECORD_UNTIL_FULL); } @@ -90,28 +87,50 @@ class TraceEventTestFixture : public testing::Test { flush_complete_event.Wait(); } + // Used when testing thread-local buffers which requires the thread initiating + // flush to have a message loop. + void EndTraceAndFlushInThreadWithMessageLoop() { + WaitableEvent flush_complete_event(false, false); + Thread flush_thread("flush"); + flush_thread.Start(); + flush_thread.message_loop()->PostTask(FROM_HERE, + base::Bind(&TraceEventTestFixture::EndTraceAndFlushAsync, + base::Unretained(this), + &flush_complete_event)); + flush_complete_event.Wait(); + } + void EndTraceAndFlushAsync(WaitableEvent* flush_complete_event) { - while (TraceLog::GetInstance()->IsEnabled()) - TraceLog::GetInstance()->SetDisabled(); + TraceLog::GetInstance()->SetDisabled(); TraceLog::GetInstance()->Flush( base::Bind(&TraceEventTestFixture::OnTraceDataCollected, base::Unretained(static_cast<TraceEventTestFixture*>(this)), base::Unretained(flush_complete_event))); } + void FlushMonitoring() { + WaitableEvent flush_complete_event(false, false); + FlushMonitoring(&flush_complete_event); + flush_complete_event.Wait(); + } + + void FlushMonitoring(WaitableEvent* flush_complete_event) { + TraceLog::GetInstance()->FlushButLeaveBufferIntact( + base::Bind(&TraceEventTestFixture::OnTraceDataCollected, + base::Unretained(static_cast<TraceEventTestFixture*>(this)), + base::Unretained(flush_complete_event))); + } + virtual void SetUp() OVERRIDE { const char* name = PlatformThread::GetName(); old_thread_name_ = name ? strdup(name) : NULL; - notifications_received_ = 0; TraceLog::DeleteForTesting(); TraceLog* tracelog = TraceLog::GetInstance(); ASSERT_TRUE(tracelog); ASSERT_FALSE(tracelog->IsEnabled()); - tracelog->SetNotificationCallback( - base::Bind(&TraceEventTestFixture::OnTraceNotification, - base::Unretained(this))); trace_buffer_.SetOutputCallback(json_output_.GetCallback()); + event_watch_notification_ = 0; } virtual void TearDown() OVERRIDE { if (TraceLog::GetInstance()) @@ -128,7 +147,6 @@ class TraceEventTestFixture : public testing::Test { base::debug::TraceResultBuffer trace_buffer_; base::debug::TraceResultBuffer::SimpleOutput json_output_; int event_watch_notification_; - int notifications_received_; private: // We want our singleton torn down after each test. @@ -333,6 +351,8 @@ std::vector<const DictionaryValue*> FindTraceEntries( return hits; } +const char* kControlCharacters = "\001\002\003\n\r"; + void TraceWithAllMacroVariants(WaitableEvent* task_complete_event) { { TRACE_EVENT_BEGIN_ETW("TRACE_EVENT_BEGIN_ETW call", 0x1122, "extrastring1"); @@ -374,10 +394,10 @@ void TraceWithAllMacroVariants(WaitableEvent* task_complete_event) { "name1", "value1", "name2", "value2"); - TRACE_EVENT_ASYNC_STEP0("all", "TRACE_EVENT_ASYNC_STEP0 call", - 5, "step1"); - TRACE_EVENT_ASYNC_STEP1("all", "TRACE_EVENT_ASYNC_STEP1 call", - 5, "step2", "name1", "value1"); + TRACE_EVENT_ASYNC_STEP_INTO0("all", "TRACE_EVENT_ASYNC_STEP_INTO0 call", + kAsyncId, "step_begin1"); + TRACE_EVENT_ASYNC_STEP_INTO1("all", "TRACE_EVENT_ASYNC_STEP_INTO1 call", + kAsyncId, "step_begin2", "name1", "value1"); TRACE_EVENT_ASYNC_END0("all", "TRACE_EVENT_ASYNC_END0 call", kAsyncId); TRACE_EVENT_ASYNC_END1("all", "TRACE_EVENT_ASYNC_END1 call", kAsyncId, @@ -412,6 +432,11 @@ void TraceWithAllMacroVariants(WaitableEvent* task_complete_event) { TRACE_EVENT_BEGIN_WITH_ID_TID_AND_TIMESTAMP0("all", "TRACE_EVENT_BEGIN_WITH_ID_TID_AND_TIMESTAMP0 call", kAsyncId2, kThreadId, 34567); + TRACE_EVENT_ASYNC_STEP_PAST0("all", "TRACE_EVENT_ASYNC_STEP_PAST0 call", + kAsyncId2, "step_end1"); + TRACE_EVENT_ASYNC_STEP_PAST1("all", "TRACE_EVENT_ASYNC_STEP_PAST1 call", + kAsyncId2, "step_end2", "name1", "value1"); + TRACE_EVENT_END_WITH_ID_TID_AND_TIMESTAMP0("all", "TRACE_EVENT_END_WITH_ID_TID_AND_TIMESTAMP0 call", kAsyncId2, kThreadId, 45678); @@ -424,6 +449,9 @@ void TraceWithAllMacroVariants(WaitableEvent* task_complete_event) { TraceScopedTrackableObject<int> trackable("all", "tracked object 2", 0x2128506); trackable.snapshot("world"); + + TRACE_EVENT1(kControlCharacters, kControlCharacters, + kControlCharacters, kControlCharacters); } // Scope close causes TRACE_EVENT0 etc to send their END events. if (task_complete_event) @@ -453,15 +481,13 @@ void ValidateAllTraceMacrosCreatedData(const ListValue& trace_parsed) { EXPECT_FIND_("TRACE_EVENT_INSTANT_ETW call"); EXPECT_FIND_("TRACE_EVENT0 call"); { - std::string ph_begin; + std::string ph; std::string ph_end; EXPECT_TRUE((item = FindTraceEntry(trace_parsed, "TRACE_EVENT0 call"))); - EXPECT_TRUE((item && item->GetString("ph", &ph_begin))); - EXPECT_TRUE((item = FindTraceEntry(trace_parsed, "TRACE_EVENT0 call", + EXPECT_TRUE((item && item->GetString("ph", &ph))); + EXPECT_EQ("X", ph); + EXPECT_FALSE((item = FindTraceEntry(trace_parsed, "TRACE_EVENT0 call", item))); - EXPECT_TRUE((item && item->GetString("ph", &ph_end))); - EXPECT_EQ("B", ph_begin); - EXPECT_EQ("E", ph_end); } EXPECT_FIND_("TRACE_EVENT1 call"); EXPECT_SUB_FIND_("name1"); @@ -533,14 +559,14 @@ void ValidateAllTraceMacrosCreatedData(const ListValue& trace_parsed) { EXPECT_SUB_FIND_("name2"); EXPECT_SUB_FIND_("value2"); - EXPECT_FIND_("TRACE_EVENT_ASYNC_STEP0 call"); + EXPECT_FIND_("TRACE_EVENT_ASYNC_STEP_INTO0 call"); EXPECT_SUB_FIND_("id"); EXPECT_SUB_FIND_(kAsyncIdStr); - EXPECT_SUB_FIND_("step1"); - EXPECT_FIND_("TRACE_EVENT_ASYNC_STEP1 call"); + EXPECT_SUB_FIND_("step_begin1"); + EXPECT_FIND_("TRACE_EVENT_ASYNC_STEP_INTO1 call"); EXPECT_SUB_FIND_("id"); EXPECT_SUB_FIND_(kAsyncIdStr); - EXPECT_SUB_FIND_("step2"); + EXPECT_SUB_FIND_("step_begin2"); EXPECT_SUB_FIND_("name1"); EXPECT_SUB_FIND_("value1"); @@ -685,6 +711,19 @@ void ValidateAllTraceMacrosCreatedData(const ListValue& trace_parsed) { EXPECT_EQ(kAsyncId2Str, id); } + EXPECT_FIND_("TRACE_EVENT_ASYNC_STEP_PAST0 call"); + { + EXPECT_SUB_FIND_("id"); + EXPECT_SUB_FIND_(kAsyncId2Str); + EXPECT_SUB_FIND_("step_end1"); + EXPECT_FIND_("TRACE_EVENT_ASYNC_STEP_PAST1 call"); + EXPECT_SUB_FIND_("id"); + EXPECT_SUB_FIND_(kAsyncId2Str); + EXPECT_SUB_FIND_("step_end2"); + EXPECT_SUB_FIND_("name1"); + EXPECT_SUB_FIND_("value1"); + } + EXPECT_FIND_("TRACE_EVENT_END_WITH_ID_TID_AND_TIMESTAMP0 call"); { int val; @@ -752,6 +791,9 @@ void ValidateAllTraceMacrosCreatedData(const ListValue& trace_parsed) { EXPECT_TRUE(item && item->GetString("id", &id)); EXPECT_EQ("0x2128506", id); } + + EXPECT_FIND_(kControlCharacters); + EXPECT_SUB_FIND_(kControlCharacters); } void TraceManyInstantEvents(int thread_id, int num_events, @@ -799,13 +841,6 @@ void ValidateInstantEventPresentOnEveryThread(const ListValue& trace_parsed, } } -void TraceCallsWithCachedCategoryPointersPointers(const char* name_str) { - TRACE_EVENT0("category name1", name_str); - TRACE_EVENT_INSTANT0("category name2", name_str, TRACE_EVENT_SCOPE_THREAD); - TRACE_EVENT_BEGIN0("category name3", name_str); - TRACE_EVENT_END0("category name4", name_str); -} - } // namespace void HighResSleepForTraceTest(base::TimeDelta elapsed) { @@ -872,7 +907,7 @@ TEST_F(TraceEventTestFixture, EnabledObserverDoesntFireOnSecondEnable) { TraceLog::GetInstance()->SetDisabled(); } -TEST_F(TraceEventTestFixture, EnabledObserverDoesntFireOnNestedDisable) { +TEST_F(TraceEventTestFixture, EnabledObserverFiresOnFirstDisable) { CategoryFilter cf_inc_all("*"); TraceLog::GetInstance()->SetEnabled(cf_inc_all, TraceLog::RECORD_UNTIL_FULL); TraceLog::GetInstance()->SetEnabled(cf_inc_all, TraceLog::RECORD_UNTIL_FULL); @@ -883,7 +918,7 @@ TEST_F(TraceEventTestFixture, EnabledObserverDoesntFireOnNestedDisable) { EXPECT_CALL(observer, OnTraceLogEnabled()) .Times(0); EXPECT_CALL(observer, OnTraceLogDisabled()) - .Times(0); + .Times(1); TraceLog::GetInstance()->SetDisabled(); testing::Mock::VerifyAndClear(&observer); @@ -1146,21 +1181,17 @@ TEST_F(TraceEventTestFixture, Categories) { TEST_F(TraceEventTestFixture, EventWatchNotification) { // Basic one occurrence. BeginTrace(); - TraceLog::GetInstance()->SetWatchEvent("cat", "event"); + TraceLog::WatchEventCallback callback = + base::Bind(&TraceEventTestFixture::OnWatchEventMatched, + base::Unretained(this)); + TraceLog::GetInstance()->SetWatchEvent("cat", "event", callback); TRACE_EVENT_INSTANT0("cat", "event", TRACE_EVENT_SCOPE_THREAD); EndTraceAndFlush(); EXPECT_EQ(event_watch_notification_, 1); - // Basic one occurrence before Set. - BeginTrace(); - TRACE_EVENT_INSTANT0("cat", "event", TRACE_EVENT_SCOPE_THREAD); - TraceLog::GetInstance()->SetWatchEvent("cat", "event"); - EndTraceAndFlush(); - EXPECT_EQ(event_watch_notification_, 1); - // Auto-reset after end trace. BeginTrace(); - TraceLog::GetInstance()->SetWatchEvent("cat", "event"); + TraceLog::GetInstance()->SetWatchEvent("cat", "event", callback); EndTraceAndFlush(); BeginTrace(); TRACE_EVENT_INSTANT0("cat", "event", TRACE_EVENT_SCOPE_THREAD); @@ -1170,7 +1201,7 @@ TEST_F(TraceEventTestFixture, EventWatchNotification) { // Multiple occurrence. BeginTrace(); int num_occurrences = 5; - TraceLog::GetInstance()->SetWatchEvent("cat", "event"); + TraceLog::GetInstance()->SetWatchEvent("cat", "event", callback); for (int i = 0; i < num_occurrences; ++i) TRACE_EVENT_INSTANT0("cat", "event", TRACE_EVENT_SCOPE_THREAD); EndTraceAndFlush(); @@ -1178,21 +1209,21 @@ TEST_F(TraceEventTestFixture, EventWatchNotification) { // Wrong category. BeginTrace(); - TraceLog::GetInstance()->SetWatchEvent("cat", "event"); + TraceLog::GetInstance()->SetWatchEvent("cat", "event", callback); TRACE_EVENT_INSTANT0("wrong_cat", "event", TRACE_EVENT_SCOPE_THREAD); EndTraceAndFlush(); EXPECT_EQ(event_watch_notification_, 0); // Wrong name. BeginTrace(); - TraceLog::GetInstance()->SetWatchEvent("cat", "event"); + TraceLog::GetInstance()->SetWatchEvent("cat", "event", callback); TRACE_EVENT_INSTANT0("cat", "wrong_event", TRACE_EVENT_SCOPE_THREAD); EndTraceAndFlush(); EXPECT_EQ(event_watch_notification_, 0); // Canceled. BeginTrace(); - TraceLog::GetInstance()->SetWatchEvent("cat", "event"); + TraceLog::GetInstance()->SetWatchEvent("cat", "event", callback); TraceLog::GetInstance()->CancelWatchEvent(); TRACE_EVENT_INSTANT0("cat", "event", TRACE_EVENT_SCOPE_THREAD); EndTraceAndFlush(); @@ -1205,10 +1236,11 @@ TEST_F(TraceEventTestFixture, AsyncBeginEndEvents) { unsigned long long id = 0xfeedbeeffeedbeefull; TRACE_EVENT_ASYNC_BEGIN0( "cat", "name1", id); - TRACE_EVENT_ASYNC_STEP0( "cat", "name1", id, "step1"); + TRACE_EVENT_ASYNC_STEP_INTO0( "cat", "name1", id, "step1"); TRACE_EVENT_ASYNC_END0("cat", "name1", id); TRACE_EVENT_BEGIN0( "cat", "name2"); TRACE_EVENT_ASYNC_BEGIN0( "cat", "name3", 0); + TRACE_EVENT_ASYNC_STEP_PAST0( "cat", "name3", 0, "step2"); EndTraceAndFlush(); @@ -1223,6 +1255,7 @@ TEST_F(TraceEventTestFixture, AsyncBeginEndEvents) { EXPECT_TRUE(FindNamePhaseKeyValue("name1", "T", "id", id_str.c_str())); EXPECT_TRUE(FindNamePhaseKeyValue("name1", "F", "id", id_str.c_str())); EXPECT_TRUE(FindNamePhaseKeyValue("name3", "S", "id", "0x0")); + EXPECT_TRUE(FindNamePhaseKeyValue("name3", "p", "id", "0x0")); // BEGIN events should not have id EXPECT_FALSE(FindNamePhaseKeyValue("name2", "B", "id", "0")); @@ -1271,48 +1304,62 @@ TEST_F(TraceEventTestFixture, StaticStringVsString) { // Make sure old events are flushed: EndTraceAndFlush(); EXPECT_EQ(0u, tracer->GetEventsSize()); + const unsigned char* category_group_enabled = + TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED("cat"); { BeginTrace(); // Test that string arguments are copied. - TRACE_EVENT2("cat", "name1", - "arg1", std::string("argval"), "arg2", std::string("argval")); + base::debug::TraceEventHandle handle1 = + trace_event_internal::AddTraceEvent( + TRACE_EVENT_PHASE_INSTANT, category_group_enabled, "name1", 0, 0, + "arg1", std::string("argval"), "arg2", std::string("argval")); // Test that static TRACE_STR_COPY string arguments are copied. - TRACE_EVENT2("cat", "name2", - "arg1", TRACE_STR_COPY("argval"), - "arg2", TRACE_STR_COPY("argval")); + base::debug::TraceEventHandle handle2 = + trace_event_internal::AddTraceEvent( + TRACE_EVENT_PHASE_INSTANT, category_group_enabled, "name2", 0, 0, + "arg1", TRACE_STR_COPY("argval"), + "arg2", TRACE_STR_COPY("argval")); size_t num_events = tracer->GetEventsSize(); EXPECT_GT(num_events, 1u); - const TraceEvent& event1 = tracer->GetEventAt(num_events - 2); - const TraceEvent& event2 = tracer->GetEventAt(num_events - 1); - EXPECT_STREQ("name1", event1.name()); - EXPECT_STREQ("name2", event2.name()); - EXPECT_TRUE(event1.parameter_copy_storage() != NULL); - EXPECT_TRUE(event2.parameter_copy_storage() != NULL); - EXPECT_GT(event1.parameter_copy_storage()->size(), 0u); - EXPECT_GT(event2.parameter_copy_storage()->size(), 0u); + const TraceEvent* event1 = tracer->GetEventByHandle(handle1); + const TraceEvent* event2 = tracer->GetEventByHandle(handle2); + ASSERT_TRUE(event1); + ASSERT_TRUE(event2); + EXPECT_STREQ("name1", event1->name()); + EXPECT_STREQ("name2", event2->name()); + EXPECT_TRUE(event1->parameter_copy_storage() != NULL); + EXPECT_TRUE(event2->parameter_copy_storage() != NULL); + EXPECT_GT(event1->parameter_copy_storage()->size(), 0u); + EXPECT_GT(event2->parameter_copy_storage()->size(), 0u); EndTraceAndFlush(); } { BeginTrace(); // Test that static literal string arguments are not copied. - TRACE_EVENT2("cat", "name1", - "arg1", "argval", "arg2", "argval"); + base::debug::TraceEventHandle handle1 = + trace_event_internal::AddTraceEvent( + TRACE_EVENT_PHASE_INSTANT, category_group_enabled, "name1", 0, 0, + "arg1", "argval", "arg2", "argval"); // Test that static TRACE_STR_COPY NULL string arguments are not copied. const char* str1 = NULL; const char* str2 = NULL; - TRACE_EVENT2("cat", "name2", - "arg1", TRACE_STR_COPY(str1), - "arg2", TRACE_STR_COPY(str2)); + base::debug::TraceEventHandle handle2 = + trace_event_internal::AddTraceEvent( + TRACE_EVENT_PHASE_INSTANT, category_group_enabled, "name2", 0, 0, + "arg1", TRACE_STR_COPY(str1), + "arg2", TRACE_STR_COPY(str2)); size_t num_events = tracer->GetEventsSize(); EXPECT_GT(num_events, 1u); - const TraceEvent& event1 = tracer->GetEventAt(num_events - 2); - const TraceEvent& event2 = tracer->GetEventAt(num_events - 1); - EXPECT_STREQ("name1", event1.name()); - EXPECT_STREQ("name2", event2.name()); - EXPECT_TRUE(event1.parameter_copy_storage() == NULL); - EXPECT_TRUE(event2.parameter_copy_storage() == NULL); + const TraceEvent* event1 = tracer->GetEventByHandle(handle1); + const TraceEvent* event2 = tracer->GetEventByHandle(handle2); + ASSERT_TRUE(event1); + ASSERT_TRUE(event2); + EXPECT_STREQ("name1", event1->name()); + EXPECT_STREQ("name2", event2->name()); + EXPECT_TRUE(event1->parameter_copy_storage() == NULL); + EXPECT_TRUE(event2->parameter_copy_storage() == NULL); EndTraceAndFlush(); } } @@ -1362,14 +1409,7 @@ TEST_F(TraceEventTestFixture, DataCapturedManyThreads) { delete task_complete_events[i]; } - WaitableEvent flush_complete_event(false, false); - Thread flush_thread("flush"); - flush_thread.Start(); - flush_thread.message_loop()->PostTask(FROM_HERE, - base::Bind(&TraceEventTestFixture::EndTraceAndFlushAsync, - base::Unretained(this), - &flush_complete_event)); - flush_complete_event.Wait(); + EndTraceAndFlushInThreadWithMessageLoop(); ValidateInstantEventPresentOnEveryThread(trace_parsed_, num_threads, num_events); @@ -1636,7 +1676,7 @@ TEST_F(TraceEventTestFixture, TraceEnableDisable) { trace_log->SetEnabled(CategoryFilter(""), TraceLog::RECORD_UNTIL_FULL); EXPECT_TRUE(trace_log->IsEnabled()); trace_log->SetDisabled(); - EXPECT_TRUE(trace_log->IsEnabled()); + EXPECT_FALSE(trace_log->IsEnabled()); trace_log->SetDisabled(); EXPECT_FALSE(trace_log->IsEnabled()); } @@ -1690,35 +1730,16 @@ TEST_F(TraceEventTestFixture, TraceCategoriesAfterNestedEnable) { trace_log->SetDisabled(); } -TEST_F(TraceEventTestFixture, TraceOptionsParsing) { - EXPECT_EQ(TraceLog::RECORD_UNTIL_FULL, - TraceLog::TraceOptionsFromString(std::string())); - - EXPECT_EQ(TraceLog::RECORD_UNTIL_FULL, - TraceLog::TraceOptionsFromString("record-until-full")); - EXPECT_EQ(TraceLog::RECORD_CONTINUOUSLY, - TraceLog::TraceOptionsFromString("record-continuously")); - EXPECT_EQ(TraceLog::RECORD_UNTIL_FULL | TraceLog::ENABLE_SAMPLING, - TraceLog::TraceOptionsFromString("enable-sampling")); - EXPECT_EQ(TraceLog::RECORD_CONTINUOUSLY | TraceLog::ENABLE_SAMPLING, - TraceLog::TraceOptionsFromString( - "record-continuously,enable-sampling")); -} - TEST_F(TraceEventTestFixture, TraceSampling) { - event_watch_notification_ = 0; TraceLog::GetInstance()->SetEnabled( CategoryFilter("*"), TraceLog::Options(TraceLog::RECORD_UNTIL_FULL | TraceLog::ENABLE_SAMPLING)); - WaitableEvent* sampled = new WaitableEvent(false, false); - TraceLog::GetInstance()->InstallWaitableEventForSamplingTesting(sampled); - TRACE_EVENT_SET_SAMPLING_STATE_FOR_BUCKET(1, "cc", "Stuff"); - sampled->Wait(); + TraceLog::GetInstance()->WaitSamplingEventForTesting(); TRACE_EVENT_SET_SAMPLING_STATE_FOR_BUCKET(1, "cc", "Things"); - sampled->Wait(); + TraceLog::GetInstance()->WaitSamplingEventForTesting(); EndTraceAndFlush(); @@ -1728,53 +1749,95 @@ TEST_F(TraceEventTestFixture, TraceSampling) { } TEST_F(TraceEventTestFixture, TraceSamplingScope) { - event_watch_notification_ = 0; TraceLog::GetInstance()->SetEnabled( CategoryFilter("*"), TraceLog::Options(TraceLog::RECORD_UNTIL_FULL | TraceLog::ENABLE_SAMPLING)); - WaitableEvent* sampled = new WaitableEvent(false, false); - TraceLog::GetInstance()->InstallWaitableEventForSamplingTesting(sampled); - TRACE_EVENT_SCOPED_SAMPLING_STATE("AAA", "name"); - sampled->Wait(); + TraceLog::GetInstance()->WaitSamplingEventForTesting(); { EXPECT_STREQ(TRACE_EVENT_GET_SAMPLING_STATE(), "AAA"); TRACE_EVENT_SCOPED_SAMPLING_STATE("BBB", "name"); - sampled->Wait(); + TraceLog::GetInstance()->WaitSamplingEventForTesting(); EXPECT_STREQ(TRACE_EVENT_GET_SAMPLING_STATE(), "BBB"); } - sampled->Wait(); + TraceLog::GetInstance()->WaitSamplingEventForTesting(); { EXPECT_STREQ(TRACE_EVENT_GET_SAMPLING_STATE(), "AAA"); TRACE_EVENT_SCOPED_SAMPLING_STATE("CCC", "name"); - sampled->Wait(); + TraceLog::GetInstance()->WaitSamplingEventForTesting(); EXPECT_STREQ(TRACE_EVENT_GET_SAMPLING_STATE(), "CCC"); } - sampled->Wait(); + TraceLog::GetInstance()->WaitSamplingEventForTesting(); { EXPECT_STREQ(TRACE_EVENT_GET_SAMPLING_STATE(), "AAA"); TRACE_EVENT_SET_SAMPLING_STATE("DDD", "name"); - sampled->Wait(); + TraceLog::GetInstance()->WaitSamplingEventForTesting(); EXPECT_STREQ(TRACE_EVENT_GET_SAMPLING_STATE(), "DDD"); } - sampled->Wait(); + TraceLog::GetInstance()->WaitSamplingEventForTesting(); EXPECT_STREQ(TRACE_EVENT_GET_SAMPLING_STATE(), "DDD"); EndTraceAndFlush(); } +TEST_F(TraceEventTestFixture, TraceContinuousSampling) { + TraceLog::GetInstance()->SetEnabled( + CategoryFilter("*"), + TraceLog::Options(TraceLog::MONITOR_SAMPLING)); + + TRACE_EVENT_SET_SAMPLING_STATE_FOR_BUCKET(1, "category", "AAA"); + TraceLog::GetInstance()->WaitSamplingEventForTesting(); + TRACE_EVENT_SET_SAMPLING_STATE_FOR_BUCKET(1, "category", "BBB"); + TraceLog::GetInstance()->WaitSamplingEventForTesting(); + + FlushMonitoring(); + + // Make sure we can get the profiled data. + EXPECT_TRUE(FindNamePhase("AAA", "P")); + EXPECT_TRUE(FindNamePhase("BBB", "P")); + + Clear(); + TraceLog::GetInstance()->WaitSamplingEventForTesting(); + + TRACE_EVENT_SET_SAMPLING_STATE_FOR_BUCKET(1, "category", "CCC"); + TraceLog::GetInstance()->WaitSamplingEventForTesting(); + TRACE_EVENT_SET_SAMPLING_STATE_FOR_BUCKET(1, "category", "DDD"); + TraceLog::GetInstance()->WaitSamplingEventForTesting(); + + FlushMonitoring(); + + // Make sure the profiled data is accumulated. + EXPECT_TRUE(FindNamePhase("AAA", "P")); + EXPECT_TRUE(FindNamePhase("BBB", "P")); + EXPECT_TRUE(FindNamePhase("CCC", "P")); + EXPECT_TRUE(FindNamePhase("DDD", "P")); + + Clear(); + + TraceLog::GetInstance()->SetDisabled(); + + // Make sure disabling the continuous sampling thread clears + // the profiled data. + EXPECT_FALSE(FindNamePhase("AAA", "P")); + EXPECT_FALSE(FindNamePhase("BBB", "P")); + EXPECT_FALSE(FindNamePhase("CCC", "P")); + EXPECT_FALSE(FindNamePhase("DDD", "P")); + + Clear(); +} + class MyData : public base::debug::ConvertableToTraceFormat { public: MyData() {} - virtual ~MyData() {} virtual void AppendAsTraceFormat(std::string* out) const OVERRIDE { out->append("{\"foo\":1}"); } private: + virtual ~MyData() {} DISALLOW_COPY_AND_ASSIGN(MyData); }; @@ -1782,36 +1845,35 @@ TEST_F(TraceEventTestFixture, ConvertableTypes) { TraceLog::GetInstance()->SetEnabled(CategoryFilter("*"), TraceLog::RECORD_UNTIL_FULL); - scoped_ptr<MyData> data(new MyData()); - scoped_ptr<MyData> data1(new MyData()); - scoped_ptr<MyData> data2(new MyData()); - TRACE_EVENT1("foo", "bar", "data", - data.PassAs<base::debug::ConvertableToTraceFormat>()); + scoped_refptr<ConvertableToTraceFormat> data(new MyData()); + scoped_refptr<ConvertableToTraceFormat> data1(new MyData()); + scoped_refptr<ConvertableToTraceFormat> data2(new MyData()); + TRACE_EVENT1("foo", "bar", "data", data); TRACE_EVENT2("foo", "baz", - "data1", data1.PassAs<base::debug::ConvertableToTraceFormat>(), - "data2", data2.PassAs<base::debug::ConvertableToTraceFormat>()); + "data1", data1, + "data2", data2); - scoped_ptr<MyData> convertData1(new MyData()); - scoped_ptr<MyData> convertData2(new MyData()); + scoped_refptr<ConvertableToTraceFormat> convertData1(new MyData()); + scoped_refptr<ConvertableToTraceFormat> convertData2(new MyData()); TRACE_EVENT2( "foo", "string_first", "str", "string value 1", "convert", - convertData1.PassAs<base::debug::ConvertableToTraceFormat>()); + convertData1); TRACE_EVENT2( "foo", "string_second", "convert", - convertData2.PassAs<base::debug::ConvertableToTraceFormat>(), + convertData2, "str", "string value 2"); EndTraceAndFlush(); // One arg version. - DictionaryValue* dict = FindNamePhase("bar", "B"); + DictionaryValue* dict = FindNamePhase("bar", "X"); ASSERT_TRUE(dict); const DictionaryValue* args_dict = NULL; @@ -1828,7 +1890,7 @@ TEST_F(TraceEventTestFixture, ConvertableTypes) { EXPECT_EQ(1, foo_val); // Two arg version. - dict = FindNamePhase("baz", "B"); + dict = FindNamePhase("baz", "X"); ASSERT_TRUE(dict); args_dict = NULL; @@ -1846,7 +1908,7 @@ TEST_F(TraceEventTestFixture, ConvertableTypes) { ASSERT_TRUE(value->GetAsDictionary(&convertable_dict)); // Convertable with other types. - dict = FindNamePhase("string_first", "B"); + dict = FindNamePhase("string_first", "X"); ASSERT_TRUE(dict); args_dict = NULL; @@ -1865,7 +1927,7 @@ TEST_F(TraceEventTestFixture, ConvertableTypes) { EXPECT_TRUE(convertable_dict->GetInteger("foo", &foo_val)); EXPECT_EQ(1, foo_val); - dict = FindNamePhase("string_second", "B"); + dict = FindNamePhase("string_second", "X"); ASSERT_TRUE(dict); args_dict = NULL; @@ -1892,19 +1954,49 @@ class TraceEventCallbackTest : public TraceEventTestFixture { s_instance = this; } virtual void TearDown() OVERRIDE { - while (TraceLog::GetInstance()->IsEnabled()) - TraceLog::GetInstance()->SetDisabled(); + TraceLog::GetInstance()->SetDisabled(); ASSERT_TRUE(!!s_instance); s_instance = NULL; TraceEventTestFixture::TearDown(); } protected: - std::vector<std::string> collected_events_; + // For TraceEventCallbackAndRecordingX tests. + void VerifyCallbackAndRecordedEvents(size_t expected_callback_count, + size_t expected_recorded_count) { + // Callback events. + EXPECT_EQ(expected_callback_count, collected_events_names_.size()); + for (size_t i = 0; i < collected_events_names_.size(); ++i) { + EXPECT_EQ("callback", collected_events_categories_[i]); + EXPECT_EQ("yes", collected_events_names_[i]); + } + + // Recorded events. + EXPECT_EQ(expected_recorded_count, trace_parsed_.GetSize()); + EXPECT_TRUE(FindTraceEntry(trace_parsed_, "recording")); + EXPECT_FALSE(FindTraceEntry(trace_parsed_, "callback")); + EXPECT_TRUE(FindTraceEntry(trace_parsed_, "yes")); + EXPECT_FALSE(FindTraceEntry(trace_parsed_, "no")); + } + + void VerifyCollectedEvent(size_t i, + unsigned phase, + const std::string& category, + const std::string& name) { + EXPECT_EQ(phase, collected_events_phases_[i]); + EXPECT_EQ(category, collected_events_categories_[i]); + EXPECT_EQ(name, collected_events_names_[i]); + } + + std::vector<std::string> collected_events_categories_; + std::vector<std::string> collected_events_names_; + std::vector<unsigned char> collected_events_phases_; + std::vector<TimeTicks> collected_events_timestamps_; static TraceEventCallbackTest* s_instance; - static void Callback(char phase, - const unsigned char* category_enabled, + static void Callback(TimeTicks timestamp, + char phase, + const unsigned char* category_group_enabled, const char* name, unsigned long long id, int num_args, @@ -1912,7 +2004,11 @@ class TraceEventCallbackTest : public TraceEventTestFixture { const unsigned char arg_types[], const unsigned long long arg_values[], unsigned char flags) { - s_instance->collected_events_.push_back(name); + s_instance->collected_events_phases_.push_back(phase); + s_instance->collected_events_categories_.push_back( + TraceLog::GetCategoryGroupName(category_group_enabled)); + s_instance->collected_events_names_.push_back(name); + s_instance->collected_events_timestamps_.push_back(timestamp); } }; @@ -1920,18 +2016,32 @@ TraceEventCallbackTest* TraceEventCallbackTest::s_instance; TEST_F(TraceEventCallbackTest, TraceEventCallback) { TRACE_EVENT_INSTANT0("all", "before enable", TRACE_EVENT_SCOPE_THREAD); - TraceLog::GetInstance()->SetEnabled(CategoryFilter("*"), - TraceLog::RECORD_UNTIL_FULL); - TRACE_EVENT_INSTANT0("all", "before callback set", TRACE_EVENT_SCOPE_THREAD); - TraceLog::GetInstance()->SetEventCallback(Callback); + TraceLog::GetInstance()->SetEventCallbackEnabled( + CategoryFilter("*"), Callback); TRACE_EVENT_INSTANT0("all", "event1", TRACE_EVENT_SCOPE_GLOBAL); TRACE_EVENT_INSTANT0("all", "event2", TRACE_EVENT_SCOPE_GLOBAL); - TraceLog::GetInstance()->SetEventCallback(NULL); + { + TRACE_EVENT0("all", "duration"); + TRACE_EVENT_INSTANT0("all", "event3", TRACE_EVENT_SCOPE_GLOBAL); + } + TraceLog::GetInstance()->SetEventCallbackDisabled(); TRACE_EVENT_INSTANT0("all", "after callback removed", TRACE_EVENT_SCOPE_GLOBAL); - ASSERT_EQ(2u, collected_events_.size()); - EXPECT_EQ("event1", collected_events_[0]); - EXPECT_EQ("event2", collected_events_[1]); + ASSERT_EQ(5u, collected_events_names_.size()); + EXPECT_EQ("event1", collected_events_names_[0]); + EXPECT_EQ(TRACE_EVENT_PHASE_INSTANT, collected_events_phases_[0]); + EXPECT_EQ("event2", collected_events_names_[1]); + EXPECT_EQ(TRACE_EVENT_PHASE_INSTANT, collected_events_phases_[1]); + EXPECT_EQ("duration", collected_events_names_[2]); + EXPECT_EQ(TRACE_EVENT_PHASE_BEGIN, collected_events_phases_[2]); + EXPECT_EQ("event3", collected_events_names_[3]); + EXPECT_EQ(TRACE_EVENT_PHASE_INSTANT, collected_events_phases_[3]); + EXPECT_EQ("duration", collected_events_names_[4]); + EXPECT_EQ(TRACE_EVENT_PHASE_END, collected_events_phases_[4]); + for (size_t i = 1; i < collected_events_timestamps_.size(); i++) { + EXPECT_LE(collected_events_timestamps_[i - 1], + collected_events_timestamps_[i]); + } } TEST_F(TraceEventCallbackTest, TraceEventCallbackWhileFull) { @@ -1939,15 +2049,236 @@ TEST_F(TraceEventCallbackTest, TraceEventCallbackWhileFull) { TraceLog::RECORD_UNTIL_FULL); do { TRACE_EVENT_INSTANT0("all", "badger badger", TRACE_EVENT_SCOPE_GLOBAL); - } while ((notifications_received_ & TraceLog::TRACE_BUFFER_FULL) == 0); - TraceLog::GetInstance()->SetEventCallback(Callback); + } while (!TraceLog::GetInstance()->BufferIsFull()); + TraceLog::GetInstance()->SetEventCallbackEnabled(CategoryFilter("*"), + Callback); TRACE_EVENT_INSTANT0("all", "a snake", TRACE_EVENT_SCOPE_GLOBAL); - TraceLog::GetInstance()->SetEventCallback(NULL); - ASSERT_EQ(1u, collected_events_.size()); - EXPECT_EQ("a snake", collected_events_[0]); + TraceLog::GetInstance()->SetEventCallbackDisabled(); + ASSERT_EQ(1u, collected_events_names_.size()); + EXPECT_EQ("a snake", collected_events_names_[0]); +} + +// 1: Enable callback, enable recording, disable callback, disable recording. +TEST_F(TraceEventCallbackTest, TraceEventCallbackAndRecording1) { + TRACE_EVENT_INSTANT0("recording", "no", TRACE_EVENT_SCOPE_GLOBAL); + TRACE_EVENT_INSTANT0("callback", "no", TRACE_EVENT_SCOPE_GLOBAL); + TraceLog::GetInstance()->SetEventCallbackEnabled(CategoryFilter("callback"), + Callback); + TRACE_EVENT_INSTANT0("recording", "no", TRACE_EVENT_SCOPE_GLOBAL); + TRACE_EVENT_INSTANT0("callback", "yes", TRACE_EVENT_SCOPE_GLOBAL); + TraceLog::GetInstance()->SetEnabled( + CategoryFilter("recording"), TraceLog::RECORD_UNTIL_FULL); + TRACE_EVENT_INSTANT0("recording", "yes", TRACE_EVENT_SCOPE_GLOBAL); + TRACE_EVENT_INSTANT0("callback", "yes", TRACE_EVENT_SCOPE_GLOBAL); + TraceLog::GetInstance()->SetEventCallbackDisabled(); + TRACE_EVENT_INSTANT0("recording", "yes", TRACE_EVENT_SCOPE_GLOBAL); + TRACE_EVENT_INSTANT0("callback", "no", TRACE_EVENT_SCOPE_GLOBAL); + EndTraceAndFlush(); + TRACE_EVENT_INSTANT0("recording", "no", TRACE_EVENT_SCOPE_GLOBAL); + TRACE_EVENT_INSTANT0("callback", "no", TRACE_EVENT_SCOPE_GLOBAL); + + VerifyCallbackAndRecordedEvents(2, 2); +} + +// 2: Enable callback, enable recording, disable recording, disable callback. +TEST_F(TraceEventCallbackTest, TraceEventCallbackAndRecording2) { + TRACE_EVENT_INSTANT0("recording", "no", TRACE_EVENT_SCOPE_GLOBAL); + TRACE_EVENT_INSTANT0("callback", "no", TRACE_EVENT_SCOPE_GLOBAL); + TraceLog::GetInstance()->SetEventCallbackEnabled(CategoryFilter("callback"), + Callback); + TRACE_EVENT_INSTANT0("recording", "no", TRACE_EVENT_SCOPE_GLOBAL); + TRACE_EVENT_INSTANT0("callback", "yes", TRACE_EVENT_SCOPE_GLOBAL); + TraceLog::GetInstance()->SetEnabled( + CategoryFilter("recording"), TraceLog::RECORD_UNTIL_FULL); + TRACE_EVENT_INSTANT0("recording", "yes", TRACE_EVENT_SCOPE_GLOBAL); + TRACE_EVENT_INSTANT0("callback", "yes", TRACE_EVENT_SCOPE_GLOBAL); + EndTraceAndFlush(); + TRACE_EVENT_INSTANT0("recording", "no", TRACE_EVENT_SCOPE_GLOBAL); + TRACE_EVENT_INSTANT0("callback", "yes", TRACE_EVENT_SCOPE_GLOBAL); + TraceLog::GetInstance()->SetEventCallbackDisabled(); + TRACE_EVENT_INSTANT0("recording", "no", TRACE_EVENT_SCOPE_GLOBAL); + TRACE_EVENT_INSTANT0("callback", "no", TRACE_EVENT_SCOPE_GLOBAL); + + VerifyCallbackAndRecordedEvents(3, 1); +} + +// 3: Enable recording, enable callback, disable callback, disable recording. +TEST_F(TraceEventCallbackTest, TraceEventCallbackAndRecording3) { + TRACE_EVENT_INSTANT0("recording", "no", TRACE_EVENT_SCOPE_GLOBAL); + TRACE_EVENT_INSTANT0("callback", "no", TRACE_EVENT_SCOPE_GLOBAL); + TraceLog::GetInstance()->SetEnabled( + CategoryFilter("recording"), TraceLog::RECORD_UNTIL_FULL); + TRACE_EVENT_INSTANT0("recording", "yes", TRACE_EVENT_SCOPE_GLOBAL); + TRACE_EVENT_INSTANT0("callback", "no", TRACE_EVENT_SCOPE_GLOBAL); + TraceLog::GetInstance()->SetEventCallbackEnabled(CategoryFilter("callback"), + Callback); + TRACE_EVENT_INSTANT0("recording", "yes", TRACE_EVENT_SCOPE_GLOBAL); + TRACE_EVENT_INSTANT0("callback", "yes", TRACE_EVENT_SCOPE_GLOBAL); + TraceLog::GetInstance()->SetEventCallbackDisabled(); + TRACE_EVENT_INSTANT0("recording", "yes", TRACE_EVENT_SCOPE_GLOBAL); + TRACE_EVENT_INSTANT0("callback", "no", TRACE_EVENT_SCOPE_GLOBAL); + EndTraceAndFlush(); + TRACE_EVENT_INSTANT0("recording", "no", TRACE_EVENT_SCOPE_GLOBAL); + TRACE_EVENT_INSTANT0("callback", "no", TRACE_EVENT_SCOPE_GLOBAL); + + VerifyCallbackAndRecordedEvents(1, 3); +} + +// 4: Enable recording, enable callback, disable recording, disable callback. +TEST_F(TraceEventCallbackTest, TraceEventCallbackAndRecording4) { + TRACE_EVENT_INSTANT0("recording", "no", TRACE_EVENT_SCOPE_GLOBAL); + TRACE_EVENT_INSTANT0("callback", "no", TRACE_EVENT_SCOPE_GLOBAL); + TraceLog::GetInstance()->SetEnabled( + CategoryFilter("recording"), TraceLog::RECORD_UNTIL_FULL); + TRACE_EVENT_INSTANT0("recording", "yes", TRACE_EVENT_SCOPE_GLOBAL); + TRACE_EVENT_INSTANT0("callback", "no", TRACE_EVENT_SCOPE_GLOBAL); + TraceLog::GetInstance()->SetEventCallbackEnabled(CategoryFilter("callback"), + Callback); + TRACE_EVENT_INSTANT0("recording", "yes", TRACE_EVENT_SCOPE_GLOBAL); + TRACE_EVENT_INSTANT0("callback", "yes", TRACE_EVENT_SCOPE_GLOBAL); + EndTraceAndFlush(); + TRACE_EVENT_INSTANT0("recording", "no", TRACE_EVENT_SCOPE_GLOBAL); + TRACE_EVENT_INSTANT0("callback", "yes", TRACE_EVENT_SCOPE_GLOBAL); + TraceLog::GetInstance()->SetEventCallbackDisabled(); + TRACE_EVENT_INSTANT0("recording", "no", TRACE_EVENT_SCOPE_GLOBAL); + TRACE_EVENT_INSTANT0("callback", "no", TRACE_EVENT_SCOPE_GLOBAL); + + VerifyCallbackAndRecordedEvents(2, 2); } -// TODO(dsinclair): Continuous Tracing unit test. +TEST_F(TraceEventCallbackTest, TraceEventCallbackAndRecordingDuration) { + TraceLog::GetInstance()->SetEventCallbackEnabled(CategoryFilter("*"), + Callback); + { + TRACE_EVENT0("callback", "duration1"); + TraceLog::GetInstance()->SetEnabled( + CategoryFilter("*"), TraceLog::RECORD_UNTIL_FULL); + TRACE_EVENT0("callback", "duration2"); + EndTraceAndFlush(); + TRACE_EVENT0("callback", "duration3"); + } + TraceLog::GetInstance()->SetEventCallbackDisabled(); + + ASSERT_EQ(6u, collected_events_names_.size()); + VerifyCollectedEvent(0, TRACE_EVENT_PHASE_BEGIN, "callback", "duration1"); + VerifyCollectedEvent(1, TRACE_EVENT_PHASE_BEGIN, "callback", "duration2"); + VerifyCollectedEvent(2, TRACE_EVENT_PHASE_BEGIN, "callback", "duration3"); + VerifyCollectedEvent(3, TRACE_EVENT_PHASE_END, "callback", "duration3"); + VerifyCollectedEvent(4, TRACE_EVENT_PHASE_END, "callback", "duration2"); + VerifyCollectedEvent(5, TRACE_EVENT_PHASE_END, "callback", "duration1"); +} + +TEST_F(TraceEventTestFixture, TraceBufferRingBufferGetReturnChunk) { + TraceLog::GetInstance()->SetEnabled(CategoryFilter("*"), + TraceLog::RECORD_CONTINUOUSLY); + TraceBuffer* buffer = TraceLog::GetInstance()->trace_buffer(); + size_t capacity = buffer->Capacity(); + size_t num_chunks = capacity / TraceBufferChunk::kTraceBufferChunkSize; + uint32 last_seq = 0; + size_t chunk_index; + EXPECT_EQ(0u, buffer->Size()); + + scoped_ptr<TraceBufferChunk*[]> chunks(new TraceBufferChunk*[num_chunks]); + for (size_t i = 0; i < num_chunks; ++i) { + chunks[i] = buffer->GetChunk(&chunk_index).release(); + EXPECT_TRUE(chunks[i]); + EXPECT_EQ(i, chunk_index); + EXPECT_GT(chunks[i]->seq(), last_seq); + EXPECT_EQ((i + 1) * TraceBufferChunk::kTraceBufferChunkSize, + buffer->Size()); + last_seq = chunks[i]->seq(); + } + + // Ring buffer is never full. + EXPECT_FALSE(buffer->IsFull()); + + // Return all chunks in original order. + for (size_t i = 0; i < num_chunks; ++i) + buffer->ReturnChunk(i, scoped_ptr<TraceBufferChunk>(chunks[i])); + + // Should recycle the chunks in the returned order. + for (size_t i = 0; i < num_chunks; ++i) { + chunks[i] = buffer->GetChunk(&chunk_index).release(); + EXPECT_TRUE(chunks[i]); + EXPECT_EQ(i, chunk_index); + EXPECT_GT(chunks[i]->seq(), last_seq); + last_seq = chunks[i]->seq(); + } + + // Return all chunks in reverse order. + for (size_t i = 0; i < num_chunks; ++i) { + buffer->ReturnChunk( + num_chunks - i - 1, + scoped_ptr<TraceBufferChunk>(chunks[num_chunks - i - 1])); + } + + // Should recycle the chunks in the returned order. + for (size_t i = 0; i < num_chunks; ++i) { + chunks[i] = buffer->GetChunk(&chunk_index).release(); + EXPECT_TRUE(chunks[i]); + EXPECT_EQ(num_chunks - i - 1, chunk_index); + EXPECT_GT(chunks[i]->seq(), last_seq); + last_seq = chunks[i]->seq(); + } + + for (size_t i = 0; i < num_chunks; ++i) + buffer->ReturnChunk(i, scoped_ptr<TraceBufferChunk>(chunks[i])); + + TraceLog::GetInstance()->SetDisabled(); +} + +TEST_F(TraceEventTestFixture, TraceBufferRingBufferHalfIteration) { + TraceLog::GetInstance()->SetEnabled(CategoryFilter("*"), + TraceLog::RECORD_CONTINUOUSLY); + TraceBuffer* buffer = TraceLog::GetInstance()->trace_buffer(); + size_t capacity = buffer->Capacity(); + size_t num_chunks = capacity / TraceBufferChunk::kTraceBufferChunkSize; + size_t chunk_index; + EXPECT_EQ(0u, buffer->Size()); + EXPECT_FALSE(buffer->NextChunk()); + + size_t half_chunks = num_chunks / 2; + scoped_ptr<TraceBufferChunk*[]> chunks(new TraceBufferChunk*[half_chunks]); + + for (size_t i = 0; i < half_chunks; ++i) { + chunks[i] = buffer->GetChunk(&chunk_index).release(); + EXPECT_TRUE(chunks[i]); + EXPECT_EQ(i, chunk_index); + } + for (size_t i = 0; i < half_chunks; ++i) + buffer->ReturnChunk(i, scoped_ptr<TraceBufferChunk>(chunks[i])); + + for (size_t i = 0; i < half_chunks; ++i) + EXPECT_EQ(chunks[i], buffer->NextChunk()); + EXPECT_FALSE(buffer->NextChunk()); + TraceLog::GetInstance()->SetDisabled(); +} + +TEST_F(TraceEventTestFixture, TraceBufferRingBufferFullIteration) { + TraceLog::GetInstance()->SetEnabled(CategoryFilter("*"), + TraceLog::RECORD_CONTINUOUSLY); + TraceBuffer* buffer = TraceLog::GetInstance()->trace_buffer(); + size_t capacity = buffer->Capacity(); + size_t num_chunks = capacity / TraceBufferChunk::kTraceBufferChunkSize; + size_t chunk_index; + EXPECT_EQ(0u, buffer->Size()); + EXPECT_FALSE(buffer->NextChunk()); + + scoped_ptr<TraceBufferChunk*[]> chunks(new TraceBufferChunk*[num_chunks]); + + for (size_t i = 0; i < num_chunks; ++i) { + chunks[i] = buffer->GetChunk(&chunk_index).release(); + EXPECT_TRUE(chunks[i]); + EXPECT_EQ(i, chunk_index); + } + for (size_t i = 0; i < num_chunks; ++i) + buffer->ReturnChunk(i, scoped_ptr<TraceBufferChunk>(chunks[i])); + + for (size_t i = 0; i < num_chunks; ++i) + EXPECT_TRUE(chunks[i] == buffer->NextChunk()); + EXPECT_FALSE(buffer->NextChunk()); + TraceLog::GetInstance()->SetDisabled(); +} // Test the category filter. TEST_F(TraceEventTestFixture, CategoryFilter) { @@ -2048,5 +2379,208 @@ TEST_F(TraceEventTestFixture, CategoryFilter) { "good_category")); } +void BlockUntilStopped(WaitableEvent* task_start_event, + WaitableEvent* task_stop_event) { + task_start_event->Signal(); + task_stop_event->Wait(); +} + +TEST_F(TraceEventTestFixture, SetCurrentThreadBlocksMessageLoopBeforeTracing) { + BeginTrace(); + + Thread thread("1"); + WaitableEvent task_complete_event(false, false); + thread.Start(); + thread.message_loop()->PostTask( + FROM_HERE, Bind(&TraceLog::SetCurrentThreadBlocksMessageLoop, + Unretained(TraceLog::GetInstance()))); + + thread.message_loop()->PostTask( + FROM_HERE, Bind(&TraceWithAllMacroVariants, &task_complete_event)); + task_complete_event.Wait(); + + WaitableEvent task_start_event(false, false); + WaitableEvent task_stop_event(false, false); + thread.message_loop()->PostTask( + FROM_HERE, Bind(&BlockUntilStopped, &task_start_event, &task_stop_event)); + task_start_event.Wait(); + + EndTraceAndFlush(); + ValidateAllTraceMacrosCreatedData(trace_parsed_); + + task_stop_event.Signal(); + thread.Stop(); +} + +void SetBlockingFlagAndBlockUntilStopped(WaitableEvent* task_start_event, + WaitableEvent* task_stop_event) { + TraceLog::GetInstance()->SetCurrentThreadBlocksMessageLoop(); + BlockUntilStopped(task_start_event, task_stop_event); +} + +TEST_F(TraceEventTestFixture, SetCurrentThreadBlocksMessageLoopAfterTracing) { + BeginTrace(); + + Thread thread("1"); + WaitableEvent task_complete_event(false, false); + thread.Start(); + + thread.message_loop()->PostTask( + FROM_HERE, Bind(&TraceWithAllMacroVariants, &task_complete_event)); + task_complete_event.Wait(); + + WaitableEvent task_start_event(false, false); + WaitableEvent task_stop_event(false, false); + thread.message_loop()->PostTask( + FROM_HERE, Bind(&SetBlockingFlagAndBlockUntilStopped, + &task_start_event, &task_stop_event)); + task_start_event.Wait(); + + EndTraceAndFlush(); + ValidateAllTraceMacrosCreatedData(trace_parsed_); + + task_stop_event.Signal(); + thread.Stop(); +} + +TEST_F(TraceEventTestFixture, ThreadOnceBlocking) { + BeginTrace(); + + Thread thread("1"); + WaitableEvent task_complete_event(false, false); + thread.Start(); + + thread.message_loop()->PostTask( + FROM_HERE, Bind(&TraceWithAllMacroVariants, &task_complete_event)); + task_complete_event.Wait(); + task_complete_event.Reset(); + + WaitableEvent task_start_event(false, false); + WaitableEvent task_stop_event(false, false); + thread.message_loop()->PostTask( + FROM_HERE, Bind(&BlockUntilStopped, &task_start_event, &task_stop_event)); + task_start_event.Wait(); + + // The thread will timeout in this flush. + EndTraceAndFlushInThreadWithMessageLoop(); + Clear(); + + // Let the thread's message loop continue to spin. + task_stop_event.Signal(); + + // The following sequence ensures that the FlushCurrentThread task has been + // executed in the thread before continuing. + task_start_event.Reset(); + task_stop_event.Reset(); + thread.message_loop()->PostTask( + FROM_HERE, Bind(&BlockUntilStopped, &task_start_event, &task_stop_event)); + task_start_event.Wait(); + task_stop_event.Signal(); + Clear(); + + // TraceLog should discover the generation mismatch and recover the thread + // local buffer for the thread without any error. + BeginTrace(); + thread.message_loop()->PostTask( + FROM_HERE, Bind(&TraceWithAllMacroVariants, &task_complete_event)); + task_complete_event.Wait(); + task_complete_event.Reset(); + EndTraceAndFlushInThreadWithMessageLoop(); + ValidateAllTraceMacrosCreatedData(trace_parsed_); +} + +std::string* g_log_buffer = NULL; +bool MockLogMessageHandler(int, const char*, int, size_t, + const std::string& str) { + if (!g_log_buffer) + g_log_buffer = new std::string(); + g_log_buffer->append(str); + return false; +} + +TEST_F(TraceEventTestFixture, EchoToConsole) { + logging::LogMessageHandlerFunction old_log_message_handler = + logging::GetLogMessageHandler(); + logging::SetLogMessageHandler(MockLogMessageHandler); + + TraceLog::GetInstance()->SetEnabled(CategoryFilter("*"), + TraceLog::ECHO_TO_CONSOLE); + TRACE_EVENT_BEGIN0("a", "begin_end"); + { + TRACE_EVENT0("b", "duration"); + TRACE_EVENT0("b1", "duration1"); + } + TRACE_EVENT_INSTANT0("c", "instant", TRACE_EVENT_SCOPE_GLOBAL); + TRACE_EVENT_END0("a", "begin_end"); + + EXPECT_NE(std::string::npos, g_log_buffer->find("begin_end[a]\x1b")); + EXPECT_NE(std::string::npos, g_log_buffer->find("| duration[b]\x1b")); + EXPECT_NE(std::string::npos, g_log_buffer->find("| | duration1[b1]\x1b")); + EXPECT_NE(std::string::npos, g_log_buffer->find("| | duration1[b1] (")); + EXPECT_NE(std::string::npos, g_log_buffer->find("| duration[b] (")); + EXPECT_NE(std::string::npos, g_log_buffer->find("| instant[c]\x1b")); + EXPECT_NE(std::string::npos, g_log_buffer->find("begin_end[a] (")); + + EndTraceAndFlush(); + delete g_log_buffer; + logging::SetLogMessageHandler(old_log_message_handler); + g_log_buffer = NULL; +} + +bool LogMessageHandlerWithTraceEvent(int, const char*, int, size_t, + const std::string&) { + TRACE_EVENT0("log", "trace_event"); + return false; +} + +TEST_F(TraceEventTestFixture, EchoToConsoleTraceEventRecursion) { + logging::LogMessageHandlerFunction old_log_message_handler = + logging::GetLogMessageHandler(); + logging::SetLogMessageHandler(LogMessageHandlerWithTraceEvent); + + TraceLog::GetInstance()->SetEnabled(CategoryFilter("*"), + TraceLog::ECHO_TO_CONSOLE); + { + // This should not cause deadlock or infinite recursion. + TRACE_EVENT0("b", "duration"); + } + + EndTraceAndFlush(); + logging::SetLogMessageHandler(old_log_message_handler); +} + +TEST_F(TraceEventTestFixture, TimeOffset) { + BeginTrace(); + // Let TraceLog timer start from 0. + TimeDelta time_offset = TimeTicks::NowFromSystemTraceTime() - TimeTicks(); + TraceLog::GetInstance()->SetTimeOffset(time_offset); + + { + TRACE_EVENT0("all", "duration1"); + TRACE_EVENT0("all", "duration2"); + } + TRACE_EVENT_BEGIN_WITH_ID_TID_AND_TIMESTAMP0( + "all", "with_timestamp", 0, 0, + TimeTicks::NowFromSystemTraceTime().ToInternalValue()); + TRACE_EVENT_END_WITH_ID_TID_AND_TIMESTAMP0( + "all", "with_timestamp", 0, 0, + TimeTicks::NowFromSystemTraceTime().ToInternalValue()); + + EndTraceAndFlush(); + + double end_time = static_cast<double>( + (TimeTicks::NowFromSystemTraceTime() - time_offset).ToInternalValue()); + double last_timestamp = 0; + for (size_t i = 0; i < trace_parsed_.GetSize(); ++i) { + const DictionaryValue* item; + EXPECT_TRUE(trace_parsed_.GetDictionary(i, &item)); + double timestamp; + EXPECT_TRUE(item->GetDouble("ts", ×tamp)); + EXPECT_GE(timestamp, last_timestamp); + EXPECT_LE(timestamp, end_time); + last_timestamp = timestamp; + } +} + } // namespace debug } // namespace base diff --git a/chromium/base/debug/trace_event_win.cc b/chromium/base/debug/trace_event_win.cc index d5a21f49268..686ade344c8 100644 --- a/chromium/base/debug/trace_event_win.cc +++ b/chromium/base/debug/trace_event_win.cc @@ -78,14 +78,17 @@ void TraceEventETWProvider::TraceEvent(const char* name, event.SetField(1, sizeof(id), &id); event.SetField(2, extra_len + 1, extra); - // See whether we're to capture a backtrace. + // These variables are declared here so that they are not out of scope when + // the event is logged. + DWORD depth; void* backtrace[32]; + + // See whether we're to capture a backtrace. if (enable_flags() & CAPTURE_STACK_TRACE) { - DWORD hash = 0; - DWORD depth = CaptureStackBackTrace(0, - arraysize(backtrace), - backtrace, - &hash); + depth = CaptureStackBackTrace(0, + arraysize(backtrace), + backtrace, + NULL); event.SetField(3, sizeof(depth), &depth); event.SetField(4, sizeof(backtrace[0]) * depth, backtrace); } diff --git a/chromium/base/debug/trace_event_win_unittest.cc b/chromium/base/debug/trace_event_win_unittest.cc index 531c5f7d867..befd0cb405f 100644 --- a/chromium/base/debug/trace_event_win_unittest.cc +++ b/chromium/base/debug/trace_event_win_unittest.cc @@ -115,7 +115,7 @@ class TraceEventWinTest: public testing::Test { } // Create the log file. - ASSERT_TRUE(file_util::CreateTemporaryFile(&log_file_)); + ASSERT_TRUE(base::CreateTemporaryFile(&log_file_)); // Create a private log session on the file. EtwTraceProperties prop; diff --git a/chromium/base/event_recorder_win.cc b/chromium/base/event_recorder_win.cc index 11bf0f0cb19..39c4220897e 100644 --- a/chromium/base/event_recorder_win.cc +++ b/chromium/base/event_recorder_win.cc @@ -49,7 +49,7 @@ bool EventRecorder::StartRecording(const FilePath& filename) { // Open the recording file. DCHECK(!file_); - file_ = file_util::OpenFile(filename, "wb+"); + file_ = OpenFile(filename, "wb+"); if (!file_) { DLOG(ERROR) << "EventRecorder could not open log file"; return false; @@ -63,7 +63,7 @@ bool EventRecorder::StartRecording(const FilePath& filename) { GetModuleHandle(NULL), 0); if (!journal_hook_) { DLOG(ERROR) << "EventRecorder Record Hook failed"; - file_util::CloseFile(file_); + CloseFile(file_); return false; } @@ -84,7 +84,7 @@ void EventRecorder::StopRecording() { ::timeEndPeriod(1); DCHECK(file_ != NULL); - file_util::CloseFile(file_); + CloseFile(file_); file_ = NULL; journal_hook_ = NULL; @@ -100,7 +100,7 @@ bool EventRecorder::StartPlayback(const FilePath& filename) { // Open the recording file. DCHECK(!file_); - file_ = file_util::OpenFile(filename, "rb"); + file_ = OpenFile(filename, "rb"); if (!file_) { DLOG(ERROR) << "EventRecorder Playback could not open log file"; return false; @@ -108,7 +108,7 @@ bool EventRecorder::StartPlayback(const FilePath& filename) { // Read the first event from the record. if (fread(&playback_msg_, sizeof(EVENTMSG), 1, file_) != 1) { DLOG(ERROR) << "EventRecorder Playback has no records!"; - file_util::CloseFile(file_); + CloseFile(file_); return false; } @@ -150,7 +150,7 @@ void EventRecorder::StopPlayback() { } DCHECK(file_ != NULL); - file_util::CloseFile(file_); + CloseFile(file_); file_ = NULL; ::timeEndPeriod(1); diff --git a/chromium/base/file_util.cc b/chromium/base/file_util.cc index e9806763855..1575a079d6b 100644 --- a/chromium/base/file_util.cc +++ b/chromium/base/file_util.cc @@ -23,8 +23,6 @@ namespace base { namespace { -const FilePath::CharType kExtensionSeparator = FILE_PATH_LITERAL('.'); - // The maximum number of 'uniquified' files we will try to create. // This is used when the filename we're trying to download is already in use, // so we create a new unique filename by appending " (nnn)" before the @@ -34,8 +32,6 @@ static const int kMaxUniqueFiles = 100; } // namespace -bool g_bug108724_debug = false; - int64 ComputeDirectorySize(const FilePath& root_path) { int64 running_size = 0; FileEnumerator file_iter(root_path, true, FileEnumerator::FILES); @@ -133,7 +129,7 @@ bool TextContentsEqual(const FilePath& filename1, const FilePath& filename2) { bool ReadFileToString(const FilePath& path, std::string* contents) { if (path.ReferencesParent()) return false; - FILE* file = file_util::OpenFile(path, "rb"); + FILE* file = OpenFile(path, "rb"); if (!file) { return false; } @@ -144,22 +140,11 @@ bool ReadFileToString(const FilePath& path, std::string* contents) { if (contents) contents->append(buf, len); } - file_util::CloseFile(file); + CloseFile(file); return true; } -} // namespace base - -// ----------------------------------------------------------------------------- - -namespace file_util { - -using base::FileEnumerator; -using base::FilePath; -using base::kExtensionSeparator; -using base::kMaxUniqueFiles; - bool IsDirectoryEmpty(const FilePath& dir_path) { FileEnumerator files(dir_path, false, FileEnumerator::FILES | FileEnumerator::DIRECTORIES); @@ -176,12 +161,12 @@ FILE* CreateAndOpenTemporaryFile(FilePath* path) { return CreateAndOpenTemporaryFileInDir(directory, path); } -bool CreateDirectory(const base::FilePath& full_path) { +bool CreateDirectory(const FilePath& full_path) { return CreateDirectoryAndGetError(full_path, NULL); } bool GetFileSize(const FilePath& file_path, int64* file_size) { - base::PlatformFileInfo info; + PlatformFileInfo info; if (!GetFileInfo(file_path, &info)) return false; *file_size = info.size; @@ -189,32 +174,26 @@ bool GetFileSize(const FilePath& file_path, int64* file_size) { } bool TouchFile(const FilePath& path, - const base::Time& last_accessed, - const base::Time& last_modified) { - int flags = base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_WRITE_ATTRIBUTES; + const Time& last_accessed, + const Time& last_modified) { + int flags = PLATFORM_FILE_OPEN | PLATFORM_FILE_WRITE_ATTRIBUTES; #if defined(OS_WIN) // On Windows, FILE_FLAG_BACKUP_SEMANTICS is needed to open a directory. if (DirectoryExists(path)) - flags |= base::PLATFORM_FILE_BACKUP_SEMANTICS; + flags |= PLATFORM_FILE_BACKUP_SEMANTICS; #endif // OS_WIN - const base::PlatformFile file = - base::CreatePlatformFile(path, flags, NULL, NULL); - if (file != base::kInvalidPlatformFileValue) { - bool result = base::TouchPlatformFile(file, last_accessed, last_modified); - base::ClosePlatformFile(file); + const PlatformFile file = CreatePlatformFile(path, flags, NULL, NULL); + if (file != kInvalidPlatformFileValue) { + bool result = TouchPlatformFile(file, last_accessed, last_modified); + ClosePlatformFile(file); return result; } return false; } -bool SetLastModifiedTime(const FilePath& path, - const base::Time& last_modified) { - return TouchFile(path, last_modified, last_modified); -} - bool CloseFile(FILE* file) { if (file == NULL) return true; @@ -239,6 +218,15 @@ bool TruncateFile(FILE* file) { return true; } +} // namespace base + +// ----------------------------------------------------------------------------- + +namespace file_util { + +using base::FilePath; +using base::kMaxUniqueFiles; + int GetUniquePathNumber( const FilePath& path, const FilePath::StringType& suffix) { diff --git a/chromium/base/file_util.h b/chromium/base/file_util.h index b4150f7368a..3f892e3ad14 100644 --- a/chromium/base/file_util.h +++ b/chromium/base/file_util.h @@ -40,8 +40,6 @@ namespace base { class Time; -extern bool g_bug108724_debug; - //----------------------------------------------------------------------------- // Functions that involve filesystem access or modification: @@ -139,13 +137,8 @@ BASE_EXPORT bool TextContentsEqual(const FilePath& filename1, // Useful for unit tests. BASE_EXPORT bool ReadFileToString(const FilePath& path, std::string* contents); -} // namespace base - -// ----------------------------------------------------------------------------- - -namespace file_util { - #if defined(OS_POSIX) + // Read exactly |bytes| bytes from file descriptor |fd|, storing the result // in |buffer|. This function is protected against EINTR and partial reads. // Returns true iff |bytes| bytes have been successfully read from |fd|. @@ -153,13 +146,12 @@ BASE_EXPORT bool ReadFromFD(int fd, char* buffer, size_t bytes); // Creates a symbolic link at |symlink| pointing to |target|. Returns // false on failure. -BASE_EXPORT bool CreateSymbolicLink(const base::FilePath& target, - const base::FilePath& symlink); +BASE_EXPORT bool CreateSymbolicLink(const FilePath& target, + const FilePath& symlink); // Reads the given |symlink| and returns where it points to in |target|. // Returns false upon failure. -BASE_EXPORT bool ReadSymbolicLink(const base::FilePath& symlink, - base::FilePath* target); +BASE_EXPORT bool ReadSymbolicLink(const FilePath& symlink, FilePath* target); // Bits ans masks of the file permission. enum FilePermissionBits { @@ -182,80 +174,95 @@ enum FilePermissionBits { // Reads the permission of the given |path|, storing the file permission // bits in |mode|. If |path| is symbolic link, |mode| is the permission of // a file which the symlink points to. -BASE_EXPORT bool GetPosixFilePermissions(const base::FilePath& path, - int* mode); +BASE_EXPORT bool GetPosixFilePermissions(const FilePath& path, int* mode); // Sets the permission of the given |path|. If |path| is symbolic link, sets // the permission of a file which the symlink points to. -BASE_EXPORT bool SetPosixFilePermissions(const base::FilePath& path, - int mode); -#endif // defined(OS_POSIX) +BASE_EXPORT bool SetPosixFilePermissions(const FilePath& path, int mode); + +#endif // OS_POSIX -// Return true if the given directory is empty -BASE_EXPORT bool IsDirectoryEmpty(const base::FilePath& dir_path); +// Returns true if the given directory is empty +BASE_EXPORT bool IsDirectoryEmpty(const FilePath& dir_path); // Get the temporary directory provided by the system. -// WARNING: DON'T USE THIS. If you want to create a temporary file, use one of -// the functions below. -BASE_EXPORT bool GetTempDir(base::FilePath* path); -// Get a temporary directory for shared memory files. +// +// WARNING: In general, you should use CreateTemporaryFile variants below +// instead of this function. Those variants will ensure that the proper +// permissions are set so that other users on the system can't edit them while +// they're open (which can lead to security issues). +BASE_EXPORT bool GetTempDir(FilePath* path); + +// Get a temporary directory for shared memory files. The directory may depend +// on whether the destination is intended for executable files, which in turn +// depends on how /dev/shmem was mounted. As a result, you must supply whether +// you intend to create executable shmem segments so this function can find +// an appropriate location. +// // Only useful on POSIX; redirects to GetTempDir() on Windows. -BASE_EXPORT bool GetShmemTempDir(base::FilePath* path, bool executable); +BASE_EXPORT bool GetShmemTempDir(bool executable, FilePath* path); +#if defined(OS_POSIX) // Get the home directory. This is more complicated than just getenv("HOME") // as it knows to fall back on getpwent() etc. -BASE_EXPORT base::FilePath GetHomeDir(); +// +// This function is not currently implemented on Windows or Mac because we +// don't use it. Generally you would use one of PathService's APP_DATA +// directories on those platforms. If we need it, this could be implemented +// there to return the appropriate directory. +BASE_EXPORT FilePath GetHomeDir(); +#endif // OS_POSIX // Creates a temporary file. The full path is placed in |path|, and the // function returns true if was successful in creating the file. The file will // be empty and all handles closed after this function returns. -BASE_EXPORT bool CreateTemporaryFile(base::FilePath* path); +BASE_EXPORT bool CreateTemporaryFile(FilePath* path); // Same as CreateTemporaryFile but the file is created in |dir|. -BASE_EXPORT bool CreateTemporaryFileInDir(const base::FilePath& dir, - base::FilePath* temp_file); +BASE_EXPORT bool CreateTemporaryFileInDir(const FilePath& dir, + FilePath* temp_file); // Create and open a temporary file. File is opened for read/write. // The full path is placed in |path|. // Returns a handle to the opened file or NULL if an error occurred. -BASE_EXPORT FILE* CreateAndOpenTemporaryFile(base::FilePath* path); +BASE_EXPORT FILE* CreateAndOpenTemporaryFile(FilePath* path); + // Like above but for shmem files. Only useful for POSIX. // The executable flag says the file needs to support using // mprotect with PROT_EXEC after mapping. -BASE_EXPORT FILE* CreateAndOpenTemporaryShmemFile(base::FilePath* path, +BASE_EXPORT FILE* CreateAndOpenTemporaryShmemFile(FilePath* path, bool executable); + // Similar to CreateAndOpenTemporaryFile, but the file is created in |dir|. -BASE_EXPORT FILE* CreateAndOpenTemporaryFileInDir(const base::FilePath& dir, - base::FilePath* path); +BASE_EXPORT FILE* CreateAndOpenTemporaryFileInDir(const FilePath& dir, + FilePath* path); // Create a new directory. If prefix is provided, the new directory name is in // the format of prefixyyyy. // NOTE: prefix is ignored in the POSIX implementation. // If success, return true and output the full path of the directory created. -BASE_EXPORT bool CreateNewTempDirectory( - const base::FilePath::StringType& prefix, - base::FilePath* new_temp_path); +BASE_EXPORT bool CreateNewTempDirectory(const FilePath::StringType& prefix, + FilePath* new_temp_path); // Create a directory within another directory. // Extra characters will be appended to |prefix| to ensure that the // new directory does not have the same name as an existing directory. -BASE_EXPORT bool CreateTemporaryDirInDir( - const base::FilePath& base_dir, - const base::FilePath::StringType& prefix, - base::FilePath* new_dir); +BASE_EXPORT bool CreateTemporaryDirInDir(const FilePath& base_dir, + const FilePath::StringType& prefix, + FilePath* new_dir); // Creates a directory, as well as creating any parent directories, if they // don't exist. Returns 'true' on successful creation, or if the directory // already exists. The directory is only readable by the current user. // Returns true on success, leaving *error unchanged. // Returns false on failure and sets *error appropriately, if it is non-NULL. -BASE_EXPORT bool CreateDirectoryAndGetError(const base::FilePath& full_path, - base::PlatformFileError* error); +BASE_EXPORT bool CreateDirectoryAndGetError(const FilePath& full_path, + PlatformFileError* error); // Backward-compatible convenience method for the above. -BASE_EXPORT bool CreateDirectory(const base::FilePath& full_path); +BASE_EXPORT bool CreateDirectory(const FilePath& full_path); // Returns the file size. Returns true on success. -BASE_EXPORT bool GetFileSize(const base::FilePath& file_path, int64* file_size); +BASE_EXPORT bool GetFileSize(const FilePath& file_path, int64* file_size); // Sets |real_path| to |path| with symbolic links and junctions expanded. // On windows, make sure the path starts with a lettered drive. @@ -263,48 +270,37 @@ BASE_EXPORT bool GetFileSize(const base::FilePath& file_path, int64* file_size); // a directory or to a nonexistent path. On windows, this function will // fail if |path| is a junction or symlink that points to an empty file, // or if |real_path| would be longer than MAX_PATH characters. -BASE_EXPORT bool NormalizeFilePath(const base::FilePath& path, - base::FilePath* real_path); +BASE_EXPORT bool NormalizeFilePath(const FilePath& path, FilePath* real_path); #if defined(OS_WIN) // Given a path in NT native form ("\Device\HarddiskVolumeXX\..."), // return in |drive_letter_path| the equivalent path that starts with // a drive letter ("C:\..."). Return false if no such path exists. -BASE_EXPORT bool DevicePathToDriveLetterPath(const base::FilePath& device_path, - base::FilePath* drive_letter_path); +BASE_EXPORT bool DevicePathToDriveLetterPath(const FilePath& device_path, + FilePath* drive_letter_path); // Given an existing file in |path|, set |real_path| to the path // in native NT format, of the form "\Device\HarddiskVolumeXX\..". // Returns false if the path can not be found. Empty files cannot // be resolved with this function. -BASE_EXPORT bool NormalizeToNativeFilePath(const base::FilePath& path, - base::FilePath* nt_path); +BASE_EXPORT bool NormalizeToNativeFilePath(const FilePath& path, + FilePath* nt_path); #endif // This function will return if the given file is a symlink or not. -BASE_EXPORT bool IsLink(const base::FilePath& file_path); +BASE_EXPORT bool IsLink(const FilePath& file_path); // Returns information about the given file path. -BASE_EXPORT bool GetFileInfo(const base::FilePath& file_path, - base::PlatformFileInfo* info); +BASE_EXPORT bool GetFileInfo(const FilePath& file_path, PlatformFileInfo* info); // Sets the time of the last access and the time of the last modification. -BASE_EXPORT bool TouchFile(const base::FilePath& path, - const base::Time& last_accessed, - const base::Time& last_modified); - -// Set the time of the last modification. Useful for unit tests. -BASE_EXPORT bool SetLastModifiedTime(const base::FilePath& path, - const base::Time& last_modified); - -#if defined(OS_POSIX) -// Store inode number of |path| in |inode|. Return true on success. -BASE_EXPORT bool GetInode(const base::FilePath& path, ino_t* inode); -#endif +BASE_EXPORT bool TouchFile(const FilePath& path, + const Time& last_accessed, + const Time& last_modified); // Wrapper for fopen-like calls. Returns non-NULL FILE* on success. -BASE_EXPORT FILE* OpenFile(const base::FilePath& filename, const char* mode); +BASE_EXPORT FILE* OpenFile(const FilePath& filename, const char* mode); // Closes file opened by OpenFile. Returns true on success. BASE_EXPORT bool CloseFile(FILE* file); @@ -317,6 +313,12 @@ BASE_EXPORT bool TruncateFile(FILE* file); // the number of read bytes, or -1 on error. BASE_EXPORT int ReadFile(const base::FilePath& filename, char* data, int size); +} // namespace base + +// ----------------------------------------------------------------------------- + +namespace file_util { + // Writes the given buffer into the file, overwriting any data that was // previously there. Returns the number of bytes written, or -1 on error. BASE_EXPORT int WriteFile(const base::FilePath& filename, const char* data, @@ -401,7 +403,7 @@ class ScopedFDClose { public: inline void operator()(int* x) const { if (x && *x >= 0) { - if (HANDLE_EINTR(close(*x)) < 0) + if (IGNORE_EINTR(close(*x)) < 0) DPLOG(ERROR) << "close"; } } diff --git a/chromium/base/file_util_android.cc b/chromium/base/file_util_android.cc index 6ac9def8c1d..def4d7cab96 100644 --- a/chromium/base/file_util_android.cc +++ b/chromium/base/file_util_android.cc @@ -7,10 +7,10 @@ #include "base/files/file_path.h" #include "base/path_service.h" -namespace file_util { +namespace base { -bool GetShmemTempDir(base::FilePath* path, bool executable) { +bool GetShmemTempDir(bool executable, base::FilePath* path) { return PathService::Get(base::DIR_CACHE, path); } -} // namespace file_util +} // namespace base diff --git a/chromium/base/file_util_mac.mm b/chromium/base/file_util_mac.mm index 9d9ac3d99b4..f8a6b6301f5 100644 --- a/chromium/base/file_util_mac.mm +++ b/chromium/base/file_util_mac.mm @@ -23,9 +23,6 @@ bool CopyFileUnsafe(const FilePath& from_path, const FilePath& to_path) { } } // namespace internal -} // namepsace base - -namespace file_util { bool GetTempDir(base::FilePath* path) { NSString* tmp = NSTemporaryDirectory(); @@ -35,8 +32,8 @@ bool GetTempDir(base::FilePath* path) { return true; } -bool GetShmemTempDir(base::FilePath* path, bool executable) { +bool GetShmemTempDir(bool executable, base::FilePath* path) { return GetTempDir(path); } -} // namespace +} // namespace base diff --git a/chromium/base/file_util_posix.cc b/chromium/base/file_util_posix.cc index 762700ae42c..ddcd5ddb912 100644 --- a/chromium/base/file_util_posix.cc +++ b/chromium/base/file_util_posix.cc @@ -24,8 +24,8 @@ #if defined(OS_MACOSX) #include <AvailabilityMacros.h> #include "base/mac/foundation_util.h" -#elif !defined(OS_ANDROID) -#include <glib.h> +#elif !defined(OS_CHROMEOS) && defined(USE_GLIB) +#include <glib.h> // for g_get_home_dir() #endif #include <fstream> @@ -43,10 +43,12 @@ #include "base/strings/stringprintf.h" #include "base/strings/sys_string_conversions.h" #include "base/strings/utf_string_conversions.h" +#include "base/sys_info.h" #include "base/threading/thread_restrictions.h" #include "base/time/time.h" #if defined(OS_ANDROID) +#include "base/android/content_uri_utils.h" #include "base/os_compat_android.h" #endif @@ -54,10 +56,6 @@ #include <grp.h> #endif -#if defined(OS_CHROMEOS) -#include "base/chromeos/chromeos_version.h" -#endif - namespace base { namespace { @@ -82,6 +80,12 @@ static int CallLstat(const char *path, stat_wrapper_t *sb) { ThreadRestrictions::AssertIOAllowed(); return lstat64(path, sb); } +#if defined(OS_ANDROID) +static int CallFstat(int fd, stat_wrapper_t *sb) { + ThreadRestrictions::AssertIOAllowed(); + return fstat64(fd, sb); +} +#endif #endif // Helper for NormalizeFilePath(), defined below. @@ -146,6 +150,47 @@ std::string TempFileName() { #endif } +// Creates and opens a temporary file in |directory|, returning the +// file descriptor. |path| is set to the temporary file path. +// This function does NOT unlink() the file. +int CreateAndOpenFdForTemporaryFile(FilePath directory, FilePath* path) { + ThreadRestrictions::AssertIOAllowed(); // For call to mkstemp(). + *path = directory.Append(base::TempFileName()); + const std::string& tmpdir_string = path->value(); + // this should be OK since mkstemp just replaces characters in place + char* buffer = const_cast<char*>(tmpdir_string.c_str()); + + return HANDLE_EINTR(mkstemp(buffer)); +} + +#if defined(OS_LINUX) +// Determine if /dev/shm files can be mapped and then mprotect'd PROT_EXEC. +// This depends on the mount options used for /dev/shm, which vary among +// different Linux distributions and possibly local configuration. It also +// depends on details of kernel--ChromeOS uses the noexec option for /dev/shm +// but its kernel allows mprotect with PROT_EXEC anyway. +bool DetermineDevShmExecutable() { + bool result = false; + FilePath path; + int fd = CreateAndOpenFdForTemporaryFile(FilePath("/dev/shm"), &path); + if (fd >= 0) { + file_util::ScopedFD shm_fd_closer(&fd); + DeleteFile(path, false); + long sysconf_result = sysconf(_SC_PAGESIZE); + CHECK_GE(sysconf_result, 0); + size_t pagesize = static_cast<size_t>(sysconf_result); + CHECK_GE(sizeof(pagesize), sizeof(sysconf_result)); + void *mapping = mmap(NULL, pagesize, PROT_READ, MAP_SHARED, fd, 0); + if (mapping != MAP_FAILED) { + if (mprotect(mapping, pagesize, PROT_READ | PROT_EXEC) == 0) + result = true; + munmap(mapping, pagesize); + } + } + return result; +} +#endif // defined(OS_LINUX) + } // namespace FilePath MakeAbsoluteFilePath(const FilePath& input) { @@ -311,6 +356,11 @@ bool CopyDirectory(const FilePath& from_path, bool PathExists(const FilePath& path) { ThreadRestrictions::AssertIOAllowed(); +#if defined(OS_ANDROID) + if (path.IsContentUri()) { + return ContentUriExists(path); + } +#endif return access(path.value().c_str(), F_OK) == 0; } @@ -327,22 +377,6 @@ bool DirectoryExists(const FilePath& path) { return false; } -} // namespace base - -// ----------------------------------------------------------------------------- - -namespace file_util { - -using base::stat_wrapper_t; -using base::CallStat; -using base::CallLstat; -using base::DirectoryExists; -using base::FileEnumerator; -using base::FilePath; -using base::MakeAbsoluteFilePath; -using base::RealPath; -using base::VerifySpecificPathControlledByUser; - bool ReadFromFD(int fd, char* buffer, size_t bytes) { size_t total_read = 0; while (total_read < bytes) { @@ -363,8 +397,7 @@ bool CreateSymbolicLink(const FilePath& target_path, symlink_path.value().c_str()) != -1; } -bool ReadSymbolicLink(const FilePath& symlink_path, - FilePath* target_path) { +bool ReadSymbolicLink(const FilePath& symlink_path, FilePath* target_path) { DCHECK(!symlink_path.empty()); DCHECK(target_path); char buf[PATH_MAX]; @@ -380,7 +413,7 @@ bool ReadSymbolicLink(const FilePath& symlink_path, } bool GetPosixFilePermissions(const FilePath& path, int* mode) { - base::ThreadRestrictions::AssertIOAllowed(); + ThreadRestrictions::AssertIOAllowed(); DCHECK(mode); stat_wrapper_t file_info; @@ -395,7 +428,7 @@ bool GetPosixFilePermissions(const FilePath& path, int* mode) { bool SetPosixFilePermissions(const FilePath& path, int mode) { - base::ThreadRestrictions::AssertIOAllowed(); + ThreadRestrictions::AssertIOAllowed(); DCHECK((mode & ~FILE_PERMISSION_MASK) == 0); // Calls stat() so that we can preserve the higher bits like S_ISGID. @@ -413,34 +446,88 @@ bool SetPosixFilePermissions(const FilePath& path, return true; } -// Creates and opens a temporary file in |directory|, returning the -// file descriptor. |path| is set to the temporary file path. -// This function does NOT unlink() the file. -int CreateAndOpenFdForTemporaryFile(FilePath directory, FilePath* path) { - base::ThreadRestrictions::AssertIOAllowed(); // For call to mkstemp(). - *path = directory.Append(base::TempFileName()); - const std::string& tmpdir_string = path->value(); - // this should be OK since mkstemp just replaces characters in place - char* buffer = const_cast<char*>(tmpdir_string.c_str()); +#if !defined(OS_MACOSX) +// This is implemented in file_util_mac.mm for Mac. +bool GetTempDir(FilePath* path) { + const char* tmp = getenv("TMPDIR"); + if (tmp) { + *path = FilePath(tmp); + } else { +#if defined(OS_ANDROID) + return PathService::Get(base::DIR_CACHE, path); +#else + *path = FilePath("/tmp"); +#endif + } + return true; +} +#endif // !defined(OS_MACOSX) - return HANDLE_EINTR(mkstemp(buffer)); +#if !defined(OS_MACOSX) && !defined(OS_ANDROID) +// This is implemented in file_util_mac.mm and file_util_android.cc for those +// platforms. +bool GetShmemTempDir(bool executable, FilePath* path) { +#if defined(OS_LINUX) + bool use_dev_shm = true; + if (executable) { + static const bool s_dev_shm_executable = DetermineDevShmExecutable(); + use_dev_shm = s_dev_shm_executable; + } + if (use_dev_shm) { + *path = FilePath("/dev/shm"); + return true; + } +#endif + return GetTempDir(path); +} +#endif // !defined(OS_MACOSX) && !defined(OS_ANDROID) + +#if !defined(OS_MACOSX) +FilePath GetHomeDir() { +#if defined(OS_CHROMEOS) + if (SysInfo::IsRunningOnChromeOS()) + return FilePath("/home/chronos/user"); +#endif + + const char* home_dir = getenv("HOME"); + if (home_dir && home_dir[0]) + return FilePath(home_dir); + +#if defined(OS_ANDROID) + DLOG(WARNING) << "OS_ANDROID: Home directory lookup not yet implemented."; +#elif defined(USE_GLIB) && !defined(OS_CHROMEOS) + // g_get_home_dir calls getpwent, which can fall through to LDAP calls. + ThreadRestrictions::AssertIOAllowed(); + + home_dir = g_get_home_dir(); + if (home_dir && home_dir[0]) + return FilePath(home_dir); +#endif + + FilePath rv; + if (GetTempDir(&rv)) + return rv; + + // Last resort. + return FilePath("/tmp"); } +#endif // !defined(OS_MACOSX) bool CreateTemporaryFile(FilePath* path) { - base::ThreadRestrictions::AssertIOAllowed(); // For call to close(). + ThreadRestrictions::AssertIOAllowed(); // For call to close(). FilePath directory; if (!GetTempDir(&directory)) return false; int fd = CreateAndOpenFdForTemporaryFile(directory, path); if (fd < 0) return false; - ignore_result(HANDLE_EINTR(close(fd))); + close(fd); return true; } FILE* CreateAndOpenTemporaryShmemFile(FilePath* path, bool executable) { FilePath directory; - if (!GetShmemTempDir(&directory, executable)) + if (!GetShmemTempDir(executable, &directory)) return NULL; return CreateAndOpenTemporaryFileInDir(directory, path); @@ -453,20 +540,20 @@ FILE* CreateAndOpenTemporaryFileInDir(const FilePath& dir, FilePath* path) { FILE* file = fdopen(fd, "a+"); if (!file) - ignore_result(HANDLE_EINTR(close(fd))); + close(fd); return file; } bool CreateTemporaryFileInDir(const FilePath& dir, FilePath* temp_file) { - base::ThreadRestrictions::AssertIOAllowed(); // For call to close(). + ThreadRestrictions::AssertIOAllowed(); // For call to close(). int fd = CreateAndOpenFdForTemporaryFile(dir, temp_file); - return ((fd >= 0) && !HANDLE_EINTR(close(fd))); + return ((fd >= 0) && !IGNORE_EINTR(close(fd))); } static bool CreateTemporaryDirInDirImpl(const FilePath& base_dir, const FilePath::StringType& name_tmpl, FilePath* new_dir) { - base::ThreadRestrictions::AssertIOAllowed(); // For call to mkdtemp(). + ThreadRestrictions::AssertIOAllowed(); // For call to mkdtemp(). DCHECK(name_tmpl.find("XXXXXX") != FilePath::StringType::npos) << "Directory name template must contain \"XXXXXX\"."; @@ -498,13 +585,12 @@ bool CreateNewTempDirectory(const FilePath::StringType& prefix, if (!GetTempDir(&tmpdir)) return false; - return CreateTemporaryDirInDirImpl(tmpdir, base::TempFileName(), - new_temp_path); + return CreateTemporaryDirInDirImpl(tmpdir, TempFileName(), new_temp_path); } bool CreateDirectoryAndGetError(const FilePath& full_path, - base::PlatformFileError* error) { - base::ThreadRestrictions::AssertIOAllowed(); // For call to mkdir(). + PlatformFileError* error) { + ThreadRestrictions::AssertIOAllowed(); // For call to mkdir(). std::vector<FilePath> subpaths; // Collect a list of all parent directories. @@ -530,29 +616,27 @@ bool CreateDirectoryAndGetError(const FilePath& full_path, int saved_errno = errno; if (!DirectoryExists(*i)) { if (error) - *error = base::ErrnoToPlatformFileError(saved_errno); + *error = ErrnoToPlatformFileError(saved_errno); return false; } } return true; } -base::FilePath MakeUniqueDirectory(const base::FilePath& path) { - const int kMaxAttempts = 20; - for (int attempts = 0; attempts < kMaxAttempts; attempts++) { - int uniquifier = - GetUniquePathNumber(path, base::FilePath::StringType()); - if (uniquifier < 0) - break; - base::FilePath test_path = (uniquifier == 0) ? path : - path.InsertBeforeExtensionASCII( - base::StringPrintf(" (%d)", uniquifier)); - if (mkdir(test_path.value().c_str(), 0777) == 0) - return test_path; - else if (errno != EEXIST) - break; - } - return base::FilePath(); +bool NormalizeFilePath(const FilePath& path, FilePath* normalized_path) { + FilePath real_path_result; + if (!RealPath(path, &real_path_result)) + return false; + + // To be consistant with windows, fail if |real_path_result| is a + // directory. + stat_wrapper_t file_info; + if (CallStat(real_path_result.value().c_str(), &file_info) != 0 || + S_ISDIR(file_info.st_mode)) + return false; + + *normalized_path = real_path_result; + return true; } // TODO(rkc): Refactor GetFileInfo and FileEnumerator to handle symlinks @@ -570,45 +654,43 @@ bool IsLink(const FilePath& file_path) { return false; } -bool GetFileInfo(const FilePath& file_path, base::PlatformFileInfo* results) { +bool GetFileInfo(const FilePath& file_path, PlatformFileInfo* results) { stat_wrapper_t file_info; - if (CallStat(file_path.value().c_str(), &file_info) != 0) - return false; +#if defined(OS_ANDROID) + if (file_path.IsContentUri()) { + int fd = OpenContentUriForRead(file_path); + if (fd < 0) + return false; + file_util::ScopedFD scoped_fd(&fd); + if (CallFstat(fd, &file_info) != 0) + return false; + } else { +#endif // defined(OS_ANDROID) + if (CallStat(file_path.value().c_str(), &file_info) != 0) + return false; +#if defined(OS_ANDROID) + } +#endif // defined(OS_ANDROID) results->is_directory = S_ISDIR(file_info.st_mode); results->size = file_info.st_size; #if defined(OS_MACOSX) - results->last_modified = base::Time::FromTimeSpec(file_info.st_mtimespec); - results->last_accessed = base::Time::FromTimeSpec(file_info.st_atimespec); - results->creation_time = base::Time::FromTimeSpec(file_info.st_ctimespec); + results->last_modified = Time::FromTimeSpec(file_info.st_mtimespec); + results->last_accessed = Time::FromTimeSpec(file_info.st_atimespec); + results->creation_time = Time::FromTimeSpec(file_info.st_ctimespec); #elif defined(OS_ANDROID) - results->last_modified = base::Time::FromTimeT(file_info.st_mtime); - results->last_accessed = base::Time::FromTimeT(file_info.st_atime); - results->creation_time = base::Time::FromTimeT(file_info.st_ctime); + results->last_modified = Time::FromTimeT(file_info.st_mtime); + results->last_accessed = Time::FromTimeT(file_info.st_atime); + results->creation_time = Time::FromTimeT(file_info.st_ctime); #else - results->last_modified = base::Time::FromTimeSpec(file_info.st_mtim); - results->last_accessed = base::Time::FromTimeSpec(file_info.st_atim); - results->creation_time = base::Time::FromTimeSpec(file_info.st_ctim); + results->last_modified = Time::FromTimeSpec(file_info.st_mtim); + results->last_accessed = Time::FromTimeSpec(file_info.st_atim); + results->creation_time = Time::FromTimeSpec(file_info.st_ctim); #endif return true; } -bool GetInode(const FilePath& path, ino_t* inode) { - base::ThreadRestrictions::AssertIOAllowed(); // For call to stat(). - struct stat buffer; - int result = stat(path.value().c_str(), &buffer); - if (result < 0) - return false; - - *inode = buffer.st_ino; - return true; -} - -FILE* OpenFile(const std::string& filename, const char* mode) { - return OpenFile(FilePath(filename), mode); -} - FILE* OpenFile(const FilePath& filename, const char* mode) { - base::ThreadRestrictions::AssertIOAllowed(); + ThreadRestrictions::AssertIOAllowed(); FILE* result = NULL; do { result = fopen(filename.value().c_str(), mode); @@ -617,17 +699,55 @@ FILE* OpenFile(const FilePath& filename, const char* mode) { } int ReadFile(const FilePath& filename, char* data, int size) { - base::ThreadRestrictions::AssertIOAllowed(); + ThreadRestrictions::AssertIOAllowed(); int fd = HANDLE_EINTR(open(filename.value().c_str(), O_RDONLY)); if (fd < 0) return -1; ssize_t bytes_read = HANDLE_EINTR(read(fd, data, size)); - if (int ret = HANDLE_EINTR(close(fd)) < 0) + if (int ret = IGNORE_EINTR(close(fd)) < 0) return ret; return bytes_read; } +} // namespace base + +// ----------------------------------------------------------------------------- + +namespace file_util { + +using base::stat_wrapper_t; +using base::CallStat; +using base::CallLstat; +using base::CreateAndOpenFdForTemporaryFile; +using base::DirectoryExists; +using base::FileEnumerator; +using base::FilePath; +using base::MakeAbsoluteFilePath; +using base::VerifySpecificPathControlledByUser; + +base::FilePath MakeUniqueDirectory(const base::FilePath& path) { + const int kMaxAttempts = 20; + for (int attempts = 0; attempts < kMaxAttempts; attempts++) { + int uniquifier = + GetUniquePathNumber(path, base::FilePath::StringType()); + if (uniquifier < 0) + break; + base::FilePath test_path = (uniquifier == 0) ? path : + path.InsertBeforeExtensionASCII( + base::StringPrintf(" (%d)", uniquifier)); + if (mkdir(test_path.value().c_str(), 0777) == 0) + return test_path; + else if (errno != EEXIST) + break; + } + return base::FilePath(); +} + +FILE* OpenFile(const std::string& filename, const char* mode) { + return OpenFile(FilePath(filename), mode); +} + int WriteFile(const FilePath& filename, const char* data, int size) { base::ThreadRestrictions::AssertIOAllowed(); int fd = HANDLE_EINTR(creat(filename.value().c_str(), 0666)); @@ -635,7 +755,7 @@ int WriteFile(const FilePath& filename, const char* data, int size) { return -1; int bytes_written = WriteFileDescriptor(fd, data, size); - if (int ret = HANDLE_EINTR(close(fd)) < 0) + if (int ret = IGNORE_EINTR(close(fd)) < 0) return ret; return bytes_written; } @@ -662,7 +782,7 @@ int AppendToFile(const FilePath& filename, const char* data, int size) { return -1; int bytes_written = WriteFileDescriptor(fd, data, size); - if (int ret = HANDLE_EINTR(close(fd)) < 0) + if (int ret = IGNORE_EINTR(close(fd)) < 0) return ret; return bytes_written; } @@ -688,117 +808,6 @@ bool SetCurrentDirectory(const FilePath& path) { return !ret; } -bool NormalizeFilePath(const FilePath& path, FilePath* normalized_path) { - FilePath real_path_result; - if (!RealPath(path, &real_path_result)) - return false; - - // To be consistant with windows, fail if |real_path_result| is a - // directory. - stat_wrapper_t file_info; - if (CallStat(real_path_result.value().c_str(), &file_info) != 0 || - S_ISDIR(file_info.st_mode)) - return false; - - *normalized_path = real_path_result; - return true; -} - -#if !defined(OS_MACOSX) -bool GetTempDir(FilePath* path) { - const char* tmp = getenv("TMPDIR"); - if (tmp) - *path = FilePath(tmp); - else -#if defined(OS_ANDROID) - return PathService::Get(base::DIR_CACHE, path); -#else - *path = FilePath("/tmp"); -#endif - return true; -} - -#if !defined(OS_ANDROID) - -#if defined(OS_LINUX) -// Determine if /dev/shm files can be mapped and then mprotect'd PROT_EXEC. -// This depends on the mount options used for /dev/shm, which vary among -// different Linux distributions and possibly local configuration. It also -// depends on details of kernel--ChromeOS uses the noexec option for /dev/shm -// but its kernel allows mprotect with PROT_EXEC anyway. - -namespace { - -bool DetermineDevShmExecutable() { - bool result = false; - FilePath path; - int fd = CreateAndOpenFdForTemporaryFile(FilePath("/dev/shm"), &path); - if (fd >= 0) { - ScopedFD shm_fd_closer(&fd); - DeleteFile(path, false); - long sysconf_result = sysconf(_SC_PAGESIZE); - CHECK_GE(sysconf_result, 0); - size_t pagesize = static_cast<size_t>(sysconf_result); - CHECK_GE(sizeof(pagesize), sizeof(sysconf_result)); - void *mapping = mmap(NULL, pagesize, PROT_READ, MAP_SHARED, fd, 0); - if (mapping != MAP_FAILED) { - if (mprotect(mapping, pagesize, PROT_READ | PROT_EXEC) == 0) - result = true; - munmap(mapping, pagesize); - } - } - return result; -} - -}; // namespace -#endif // defined(OS_LINUX) - -bool GetShmemTempDir(FilePath* path, bool executable) { -#if defined(OS_LINUX) - bool use_dev_shm = true; - if (executable) { - static const bool s_dev_shm_executable = DetermineDevShmExecutable(); - use_dev_shm = s_dev_shm_executable; - } - if (use_dev_shm) { - *path = FilePath("/dev/shm"); - return true; - } -#endif - return GetTempDir(path); -} -#endif // !defined(OS_ANDROID) - -FilePath GetHomeDir() { -#if defined(OS_CHROMEOS) - if (base::chromeos::IsRunningOnChromeOS()) - return FilePath("/home/chronos/user"); -#endif - - const char* home_dir = getenv("HOME"); - if (home_dir && home_dir[0]) - return FilePath(home_dir); - -#if defined(OS_ANDROID) - DLOG(WARNING) << "OS_ANDROID: Home directory lookup not yet implemented."; -#else - // g_get_home_dir calls getpwent, which can fall through to LDAP calls. - base::ThreadRestrictions::AssertIOAllowed(); - - home_dir = g_get_home_dir(); - if (home_dir && home_dir[0]) - return FilePath(home_dir); -#endif - - FilePath rv; - if (file_util::GetTempDir(&rv)) - return rv; - - // Last resort. - return FilePath("/tmp"); -} -#endif // !defined(OS_MACOSX) - bool VerifyPathControlledByUser(const FilePath& base, const FilePath& path, uid_t owner_uid, @@ -914,7 +923,7 @@ bool CopyFileUnsafe(const FilePath& from_path, const FilePath& to_path) { int outfile = HANDLE_EINTR(creat(to_path.value().c_str(), 0666)); if (outfile < 0) { - ignore_result(HANDLE_EINTR(close(infile))); + close(infile); return false; } @@ -945,9 +954,9 @@ bool CopyFileUnsafe(const FilePath& from_path, const FilePath& to_path) { } while (bytes_written_per_read < bytes_read); } - if (HANDLE_EINTR(close(infile)) < 0) + if (IGNORE_EINTR(close(infile)) < 0) result = false; - if (HANDLE_EINTR(close(outfile)) < 0) + if (IGNORE_EINTR(close(outfile)) < 0) result = false; return result; diff --git a/chromium/base/file_util_unittest.cc b/chromium/base/file_util_unittest.cc index 787b6d50ba7..b65e171719f 100644 --- a/chromium/base/file_util_unittest.cc +++ b/chromium/base/file_util_unittest.cc @@ -33,14 +33,14 @@ #include "base/win/windows_version.h" #endif +#if defined(OS_ANDROID) +#include "base/android/content_uri_utils.h" +#endif + // This macro helps avoid wrapped lines in the test structs. #define FPL(x) FILE_PATH_LITERAL(x) -using base::DirectoryExists; -using base::FileEnumerator; -using base::FilePath; -using base::PathIsWritable; -using base::TextContentsEqual; +namespace base { namespace { @@ -142,7 +142,7 @@ class ReparsePoint { bool IsValid() { return created_; } private: - base::win::ScopedHandle dir_; + win::ScopedHandle dir_; bool created_; DISALLOW_COPY_AND_ASSIGN(ReparsePoint); }; @@ -160,10 +160,10 @@ void ChangePosixFilePermissions(const FilePath& path, << "Can't set and clear the same bits."; int mode = 0; - ASSERT_TRUE(file_util::GetPosixFilePermissions(path, &mode)); + ASSERT_TRUE(GetPosixFilePermissions(path, &mode)); mode |= mode_bits_to_set; mode &= ~mode_bits_to_clear; - ASSERT_TRUE(file_util::SetPosixFilePermissions(path, mode)); + ASSERT_TRUE(SetPosixFilePermissions(path, mode)); } #endif // defined(OS_POSIX) @@ -181,7 +181,7 @@ class FileUtilTest : public PlatformTest { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); } - base::ScopedTempDir temp_dir_; + ScopedTempDir temp_dir_; }; // Collects all the results from the given file enumerator, and provides an @@ -244,133 +244,31 @@ uint64 FileTimeAsUint64(const FILETIME& ft) { } #endif -const struct append_case { - const wchar_t* path; - const wchar_t* ending; - const wchar_t* result; -} append_cases[] = { -#if defined(OS_WIN) - {L"c:\\colon\\backslash", L"path", L"c:\\colon\\backslash\\path"}, - {L"c:\\colon\\backslash\\", L"path", L"c:\\colon\\backslash\\path"}, - {L"c:\\colon\\backslash\\\\", L"path", L"c:\\colon\\backslash\\\\path"}, - {L"c:\\colon\\backslash\\", L"", L"c:\\colon\\backslash\\"}, - {L"c:\\colon\\backslash", L"", L"c:\\colon\\backslash\\"}, - {L"", L"path", L"\\path"}, - {L"", L"", L"\\"}, -#elif defined(OS_POSIX) - {L"/foo/bar", L"path", L"/foo/bar/path"}, - {L"/foo/bar/", L"path", L"/foo/bar/path"}, - {L"/foo/bar//", L"path", L"/foo/bar//path"}, - {L"/foo/bar/", L"", L"/foo/bar/"}, - {L"/foo/bar", L"", L"/foo/bar/"}, - {L"", L"path", L"/path"}, - {L"", L"", L"/"}, -#endif -}; - -static const struct filename_case { - const wchar_t* path; - const wchar_t* filename; -} filename_cases[] = { -#if defined(OS_WIN) - {L"c:\\colon\\backslash", L"backslash"}, - {L"c:\\colon\\backslash\\", L""}, - {L"\\\\filename.exe", L"filename.exe"}, - {L"filename.exe", L"filename.exe"}, - {L"", L""}, - {L"\\\\\\", L""}, - {L"c:/colon/backslash", L"backslash"}, - {L"c:/colon/backslash/", L""}, - {L"//////", L""}, - {L"///filename.exe", L"filename.exe"}, -#elif defined(OS_POSIX) - {L"/foo/bar", L"bar"}, - {L"/foo/bar/", L""}, - {L"/filename.exe", L"filename.exe"}, - {L"filename.exe", L"filename.exe"}, - {L"", L""}, - {L"/", L""}, -#endif -}; - -// Test finding the file type from a path name -static const struct extension_case { - const wchar_t* path; - const wchar_t* extension; -} extension_cases[] = { -#if defined(OS_WIN) - {L"C:\\colon\\backslash\\filename.extension", L"extension"}, - {L"C:\\colon\\backslash\\filename.", L""}, - {L"C:\\colon\\backslash\\filename", L""}, - {L"C:\\colon\\backslash\\", L""}, - {L"C:\\colon\\backslash.\\", L""}, - {L"C:\\colon\\backslash\filename.extension.extension2", L"extension2"}, -#elif defined(OS_POSIX) - {L"/foo/bar/filename.extension", L"extension"}, - {L"/foo/bar/filename.", L""}, - {L"/foo/bar/filename", L""}, - {L"/foo/bar/", L""}, - {L"/foo/bar./", L""}, - {L"/foo/bar/filename.extension.extension2", L"extension2"}, - {L".", L""}, - {L"..", L""}, - {L"./foo", L""}, - {L"./foo.extension", L"extension"}, - {L"/foo.extension1/bar.extension2", L"extension2"}, -#endif -}; - -// Test finding the directory component of a path -static const struct dir_case { - const wchar_t* full_path; - const wchar_t* directory; -} dir_cases[] = { -#if defined(OS_WIN) - {L"C:\\WINDOWS\\system32\\gdi32.dll", L"C:\\WINDOWS\\system32"}, - {L"C:\\WINDOWS\\system32\\not_exist_thx_1138", L"C:\\WINDOWS\\system32"}, - {L"C:\\WINDOWS\\system32\\", L"C:\\WINDOWS\\system32"}, - {L"C:\\WINDOWS\\system32\\\\", L"C:\\WINDOWS\\system32"}, - {L"C:\\WINDOWS\\system32", L"C:\\WINDOWS"}, - {L"C:\\WINDOWS\\system32.\\", L"C:\\WINDOWS\\system32."}, - {L"C:\\", L"C:\\"}, -#elif defined(OS_POSIX) - {L"/foo/bar/gdi32.dll", L"/foo/bar"}, - {L"/foo/bar/not_exist_thx_1138", L"/foo/bar"}, - {L"/foo/bar/", L"/foo/bar"}, - {L"/foo/bar//", L"/foo/bar"}, - {L"/foo/bar", L"/foo"}, - {L"/foo/bar./", L"/foo/bar."}, - {L"/", L"/"}, - {L".", L"."}, - {L"..", L"."}, // yes, ".." technically lives in "." -#endif -}; - TEST_F(FileUtilTest, FileAndDirectorySize) { // Create three files of 20, 30 and 3 chars (utf8). ComputeDirectorySize // should return 53 bytes. FilePath file_01 = temp_dir_.path().Append(FPL("The file 01.txt")); CreateTextFile(file_01, L"12345678901234567890"); int64 size_f1 = 0; - ASSERT_TRUE(file_util::GetFileSize(file_01, &size_f1)); + ASSERT_TRUE(GetFileSize(file_01, &size_f1)); EXPECT_EQ(20ll, size_f1); FilePath subdir_path = temp_dir_.path().Append(FPL("Level2")); - file_util::CreateDirectory(subdir_path); + CreateDirectory(subdir_path); FilePath file_02 = subdir_path.Append(FPL("The file 02.txt")); CreateTextFile(file_02, L"123456789012345678901234567890"); int64 size_f2 = 0; - ASSERT_TRUE(file_util::GetFileSize(file_02, &size_f2)); + ASSERT_TRUE(GetFileSize(file_02, &size_f2)); EXPECT_EQ(30ll, size_f2); FilePath subsubdir_path = subdir_path.Append(FPL("Level3")); - file_util::CreateDirectory(subsubdir_path); + CreateDirectory(subsubdir_path); FilePath file_03 = subsubdir_path.Append(FPL("The file 03.txt")); CreateTextFile(file_03, L"123"); - int64 computed_size = base::ComputeDirectorySize(temp_dir_.path()); + int64 computed_size = ComputeDirectorySize(temp_dir_.path()); EXPECT_EQ(size_f1 + size_f2 + 3, computed_size); } @@ -380,23 +278,20 @@ TEST_F(FileUtilTest, NormalizeFilePathBasic) { FilePath file_a_path = temp_dir_.path().Append(FPL("file_a")); FilePath dir_path = temp_dir_.path().Append(FPL("dir")); FilePath file_b_path = dir_path.Append(FPL("file_b")); - file_util::CreateDirectory(dir_path); + CreateDirectory(dir_path); FilePath normalized_file_a_path, normalized_file_b_path; - ASSERT_FALSE(base::PathExists(file_a_path)); - ASSERT_FALSE(file_util::NormalizeFilePath(file_a_path, - &normalized_file_a_path)) + ASSERT_FALSE(PathExists(file_a_path)); + ASSERT_FALSE(NormalizeFilePath(file_a_path, &normalized_file_a_path)) << "NormalizeFilePath() should fail on nonexistent paths."; CreateTextFile(file_a_path, bogus_content); - ASSERT_TRUE(base::PathExists(file_a_path)); - ASSERT_TRUE(file_util::NormalizeFilePath(file_a_path, - &normalized_file_a_path)); + ASSERT_TRUE(PathExists(file_a_path)); + ASSERT_TRUE(NormalizeFilePath(file_a_path, &normalized_file_a_path)); CreateTextFile(file_b_path, bogus_content); - ASSERT_TRUE(base::PathExists(file_b_path)); - ASSERT_TRUE(file_util::NormalizeFilePath(file_b_path, - &normalized_file_b_path)); + ASSERT_TRUE(PathExists(file_b_path)); + ASSERT_TRUE(NormalizeFilePath(file_b_path, &normalized_file_b_path)); // Beacuse this test created |dir_path|, we know it is not a link // or junction. So, the real path of the directory holding file a @@ -423,10 +318,10 @@ TEST_F(FileUtilTest, NormalizeFilePathReparsePoints) { // |-> to_sub_long (reparse point to temp_dir\sub_a\long_name_\sub_long) FilePath base_a = temp_dir_.path().Append(FPL("base_a")); - ASSERT_TRUE(file_util::CreateDirectory(base_a)); + ASSERT_TRUE(CreateDirectory(base_a)); FilePath sub_a = base_a.Append(FPL("sub_a")); - ASSERT_TRUE(file_util::CreateDirectory(sub_a)); + ASSERT_TRUE(CreateDirectory(sub_a)); FilePath file_txt = sub_a.Append(FPL("file.txt")); CreateTextFile(file_txt, bogus_content); @@ -454,42 +349,42 @@ TEST_F(FileUtilTest, NormalizeFilePathReparsePoints) { ASSERT_EQ(MAX_PATH - kCreateDirLimit, deep_file.value().length()); FilePath sub_long = deep_file.DirName(); - ASSERT_TRUE(file_util::CreateDirectory(sub_long)); + ASSERT_TRUE(CreateDirectory(sub_long)); CreateTextFile(deep_file, bogus_content); FilePath base_b = temp_dir_.path().Append(FPL("base_b")); - ASSERT_TRUE(file_util::CreateDirectory(base_b)); + ASSERT_TRUE(CreateDirectory(base_b)); FilePath to_sub_a = base_b.Append(FPL("to_sub_a")); - ASSERT_TRUE(file_util::CreateDirectory(to_sub_a)); + ASSERT_TRUE(CreateDirectory(to_sub_a)); FilePath normalized_path; { ReparsePoint reparse_to_sub_a(to_sub_a, sub_a); ASSERT_TRUE(reparse_to_sub_a.IsValid()); FilePath to_base_b = base_b.Append(FPL("to_base_b")); - ASSERT_TRUE(file_util::CreateDirectory(to_base_b)); + ASSERT_TRUE(CreateDirectory(to_base_b)); ReparsePoint reparse_to_base_b(to_base_b, base_b); ASSERT_TRUE(reparse_to_base_b.IsValid()); FilePath to_sub_long = base_b.Append(FPL("to_sub_long")); - ASSERT_TRUE(file_util::CreateDirectory(to_sub_long)); + ASSERT_TRUE(CreateDirectory(to_sub_long)); ReparsePoint reparse_to_sub_long(to_sub_long, sub_long); ASSERT_TRUE(reparse_to_sub_long.IsValid()); // Normalize a junction free path: base_a\sub_a\file.txt . - ASSERT_TRUE(file_util::NormalizeFilePath(file_txt, &normalized_path)); + ASSERT_TRUE(NormalizeFilePath(file_txt, &normalized_path)); ASSERT_STREQ(file_txt.value().c_str(), normalized_path.value().c_str()); // Check that the path base_b\to_sub_a\file.txt can be normalized to exclude // the junction to_sub_a. - ASSERT_TRUE(file_util::NormalizeFilePath(to_sub_a.Append(FPL("file.txt")), + ASSERT_TRUE(NormalizeFilePath(to_sub_a.Append(FPL("file.txt")), &normalized_path)); ASSERT_STREQ(file_txt.value().c_str(), normalized_path.value().c_str()); // Check that the path base_b\to_base_b\to_base_b\to_sub_a\file.txt can be // normalized to exclude junctions to_base_b and to_sub_a . - ASSERT_TRUE(file_util::NormalizeFilePath(base_b.Append(FPL("to_base_b")) + ASSERT_TRUE(NormalizeFilePath(base_b.Append(FPL("to_base_b")) .Append(FPL("to_base_b")) .Append(FPL("to_sub_a")) .Append(FPL("file.txt")), @@ -507,18 +402,18 @@ TEST_F(FileUtilTest, NormalizeFilePathReparsePoints) { long_path = long_path.Append(FPL("to_sub_a")) .Append(FPL("file.txt")); - ASSERT_FALSE(file_util::NormalizeFilePath(long_path, &normalized_path)); + ASSERT_FALSE(NormalizeFilePath(long_path, &normalized_path)); // Normalizing the junction to deep.txt should fail, because the expanded // path to deep.txt is longer than MAX_PATH. - ASSERT_FALSE(file_util::NormalizeFilePath(to_sub_long.Append(deep_txt), + ASSERT_FALSE(NormalizeFilePath(to_sub_long.Append(deep_txt), &normalized_path)); // Delete the reparse points, and see that NormalizeFilePath() fails // to traverse them. } - ASSERT_FALSE(file_util::NormalizeFilePath(to_sub_a.Append(FPL("file.txt")), + ASSERT_FALSE(NormalizeFilePath(to_sub_a.Append(FPL("file.txt")), &normalized_path)); } @@ -539,14 +434,13 @@ TEST_F(FileUtilTest, DevicePathToDriveLetter) { // Run DevicePathToDriveLetterPath() on the NT style path we got from // QueryDosDevice(). Expect the drive letter we started with. - ASSERT_TRUE(file_util::DevicePathToDriveLetterPath(actual_device_path, - &win32_path)); + ASSERT_TRUE(DevicePathToDriveLetterPath(actual_device_path, &win32_path)); ASSERT_EQ(real_drive_letter, win32_path.value()); // Add some directories to the path. Expect those extra path componenets // to be preserved. FilePath kRelativePath(FPL("dir1\\dir2\\file.txt")); - ASSERT_TRUE(file_util::DevicePathToDriveLetterPath( + ASSERT_TRUE(DevicePathToDriveLetterPath( actual_device_path.Append(kRelativePath), &win32_path)); EXPECT_EQ(FilePath(real_drive_letter + L"\\").Append(kRelativePath).value(), @@ -564,11 +458,10 @@ TEST_F(FileUtilTest, DevicePathToDriveLetter) { ASSERT_LT(0, new_length); FilePath prefix_of_real_device_path( actual_device_path.value().substr(0, new_length)); - ASSERT_FALSE(file_util::DevicePathToDriveLetterPath( - prefix_of_real_device_path, - &win32_path)); + ASSERT_FALSE(DevicePathToDriveLetterPath(prefix_of_real_device_path, + &win32_path)); - ASSERT_FALSE(file_util::DevicePathToDriveLetterPath( + ASSERT_FALSE(DevicePathToDriveLetterPath( prefix_of_real_device_path.Append(kRelativePath), &win32_path)); @@ -583,19 +476,19 @@ TEST_F(FileUtilTest, DevicePathToDriveLetter) { FilePath real_device_path_plus_numbers( actual_device_path.value() + kExtraChars); - ASSERT_FALSE(file_util::DevicePathToDriveLetterPath( + ASSERT_FALSE(DevicePathToDriveLetterPath( real_device_path_plus_numbers, &win32_path)); - ASSERT_FALSE(file_util::DevicePathToDriveLetterPath( + ASSERT_FALSE(DevicePathToDriveLetterPath( real_device_path_plus_numbers.Append(kRelativePath), &win32_path)); } TEST_F(FileUtilTest, GetPlatformFileInfoForDirectory) { FilePath empty_dir = temp_dir_.path().Append(FPL("gpfi_test")); - ASSERT_TRUE(file_util::CreateDirectory(empty_dir)); - base::win::ScopedHandle dir( + ASSERT_TRUE(CreateDirectory(empty_dir)); + win::ScopedHandle dir( ::CreateFile(empty_dir.value().c_str(), FILE_ALL_ACCESS, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, @@ -604,8 +497,8 @@ TEST_F(FileUtilTest, GetPlatformFileInfoForDirectory) { FILE_FLAG_BACKUP_SEMANTICS, // Needed to open a directory. NULL)); ASSERT_TRUE(dir.IsValid()); - base::PlatformFileInfo info; - EXPECT_TRUE(base::GetPlatformFileInfo(dir.Get(), &info)); + PlatformFileInfo info; + EXPECT_TRUE(GetPlatformFileInfo(dir.Get(), &info)); EXPECT_TRUE(info.is_directory); EXPECT_FALSE(info.is_symbolic_link); EXPECT_EQ(0, info.size); @@ -620,7 +513,7 @@ TEST_F(FileUtilTest, CreateTemporaryFileInDirLongPathTest) { const FilePath::CharType kLongDirName[] = FPL("A long path"); const FilePath::CharType kTestSubDirName[] = FPL("test"); FilePath long_test_dir = temp_dir_.path().Append(kLongDirName); - ASSERT_TRUE(file_util::CreateDirectory(long_test_dir)); + ASSERT_TRUE(CreateDirectory(long_test_dir)); // kLongDirName is not a 8.3 component. So GetShortName() should give us a // different short name. @@ -633,9 +526,9 @@ TEST_F(FileUtilTest, CreateTemporaryFileInDirLongPathTest) { ASSERT_STRNE(kLongDirName, short_test_dir.BaseName().value().c_str()); FilePath temp_file; - ASSERT_TRUE(file_util::CreateTemporaryFileInDir(short_test_dir, &temp_file)); + ASSERT_TRUE(CreateTemporaryFileInDir(short_test_dir, &temp_file)); EXPECT_STREQ(kLongDirName, temp_file.DirName().BaseName().value().c_str()); - EXPECT_TRUE(base::PathExists(temp_file)); + EXPECT_TRUE(PathExists(temp_file)); // Create a subdirectory of |long_test_dir| and make |long_test_dir| // unreadable. We should still be able to create a temp file in the @@ -645,14 +538,14 @@ TEST_F(FileUtilTest, CreateTemporaryFileInDirLongPathTest) { // directories. (Note that this assumption is true for NTFS, but not for some // network file systems. E.g. AFS). FilePath access_test_dir = long_test_dir.Append(kTestSubDirName); - ASSERT_TRUE(file_util::CreateDirectory(access_test_dir)); + ASSERT_TRUE(CreateDirectory(access_test_dir)); file_util::PermissionRestorer long_test_dir_restorer(long_test_dir); ASSERT_TRUE(file_util::MakeFileUnreadable(long_test_dir)); // Use the short form of the directory to create a temporary filename. - ASSERT_TRUE(file_util::CreateTemporaryFileInDir( + ASSERT_TRUE(CreateTemporaryFileInDir( short_test_dir.Append(kTestSubDirName), &temp_file)); - EXPECT_TRUE(base::PathExists(temp_file)); + EXPECT_TRUE(PathExists(temp_file)); EXPECT_TRUE(short_test_dir.IsParent(temp_file.DirName())); // Check that the long path can't be determined for |temp_file|. @@ -670,7 +563,7 @@ TEST_F(FileUtilTest, CreateAndReadSymlinks) { FilePath link_to = temp_dir_.path().Append(FPL("to_file")); CreateTextFile(link_to, bogus_content); - ASSERT_TRUE(file_util::CreateSymbolicLink(link_to, link_from)) + ASSERT_TRUE(CreateSymbolicLink(link_to, link_from)) << "Failed to create file symlink."; // If we created the link properly, we should be able to read the contents @@ -679,21 +572,21 @@ TEST_F(FileUtilTest, CreateAndReadSymlinks) { EXPECT_EQ(bogus_content, contents); FilePath result; - ASSERT_TRUE(file_util::ReadSymbolicLink(link_from, &result)); + ASSERT_TRUE(ReadSymbolicLink(link_from, &result)); EXPECT_EQ(link_to.value(), result.value()); // Link to a directory. link_from = temp_dir_.path().Append(FPL("from_dir")); link_to = temp_dir_.path().Append(FPL("to_dir")); - ASSERT_TRUE(file_util::CreateDirectory(link_to)); - ASSERT_TRUE(file_util::CreateSymbolicLink(link_to, link_from)) + ASSERT_TRUE(CreateDirectory(link_to)); + ASSERT_TRUE(CreateSymbolicLink(link_to, link_from)) << "Failed to create directory symlink."; // Test failures. - EXPECT_FALSE(file_util::CreateSymbolicLink(link_to, link_to)); - EXPECT_FALSE(file_util::ReadSymbolicLink(link_to, &result)); + EXPECT_FALSE(CreateSymbolicLink(link_to, link_to)); + EXPECT_FALSE(ReadSymbolicLink(link_to, &result)); FilePath missing = temp_dir_.path().Append(FPL("missing")); - EXPECT_FALSE(file_util::ReadSymbolicLink(missing, &result)); + EXPECT_FALSE(ReadSymbolicLink(missing, &result)); } // The following test of NormalizeFilePath() require that we create a symlink. @@ -707,12 +600,12 @@ TEST_F(FileUtilTest, NormalizeFilePathSymlinks) { FilePath link_to = temp_dir_.path().Append(FPL("to_file")); CreateTextFile(link_to, bogus_content); - ASSERT_TRUE(file_util::CreateSymbolicLink(link_to, link_from)) + ASSERT_TRUE(CreateSymbolicLink(link_to, link_from)) << "Failed to create file symlink."; // Check that NormalizeFilePath sees the link. FilePath normalized_path; - ASSERT_TRUE(file_util::NormalizeFilePath(link_from, &normalized_path)); + ASSERT_TRUE(NormalizeFilePath(link_from, &normalized_path)); EXPECT_NE(link_from, link_to); EXPECT_EQ(link_to.BaseName().value(), normalized_path.BaseName().value()); EXPECT_EQ(link_to.BaseName().value(), normalized_path.BaseName().value()); @@ -720,54 +613,54 @@ TEST_F(FileUtilTest, NormalizeFilePathSymlinks) { // Link to a directory. link_from = temp_dir_.path().Append(FPL("from_dir")); link_to = temp_dir_.path().Append(FPL("to_dir")); - ASSERT_TRUE(file_util::CreateDirectory(link_to)); - ASSERT_TRUE(file_util::CreateSymbolicLink(link_to, link_from)) + ASSERT_TRUE(CreateDirectory(link_to)); + ASSERT_TRUE(CreateSymbolicLink(link_to, link_from)) << "Failed to create directory symlink."; - EXPECT_FALSE(file_util::NormalizeFilePath(link_from, &normalized_path)) + EXPECT_FALSE(NormalizeFilePath(link_from, &normalized_path)) << "Links to directories should return false."; // Test that a loop in the links causes NormalizeFilePath() to return false. link_from = temp_dir_.path().Append(FPL("link_a")); link_to = temp_dir_.path().Append(FPL("link_b")); - ASSERT_TRUE(file_util::CreateSymbolicLink(link_to, link_from)) + ASSERT_TRUE(CreateSymbolicLink(link_to, link_from)) << "Failed to create loop symlink a."; - ASSERT_TRUE(file_util::CreateSymbolicLink(link_from, link_to)) + ASSERT_TRUE(CreateSymbolicLink(link_from, link_to)) << "Failed to create loop symlink b."; // Infinite loop! - EXPECT_FALSE(file_util::NormalizeFilePath(link_from, &normalized_path)); + EXPECT_FALSE(NormalizeFilePath(link_from, &normalized_path)); } #endif // defined(OS_POSIX) TEST_F(FileUtilTest, DeleteNonExistent) { FilePath non_existent = temp_dir_.path().AppendASCII("bogus_file_dne.foobar"); - ASSERT_FALSE(base::PathExists(non_existent)); + ASSERT_FALSE(PathExists(non_existent)); - EXPECT_TRUE(base::DeleteFile(non_existent, false)); - ASSERT_FALSE(base::PathExists(non_existent)); - EXPECT_TRUE(base::DeleteFile(non_existent, true)); - ASSERT_FALSE(base::PathExists(non_existent)); + EXPECT_TRUE(DeleteFile(non_existent, false)); + ASSERT_FALSE(PathExists(non_existent)); + EXPECT_TRUE(DeleteFile(non_existent, true)); + ASSERT_FALSE(PathExists(non_existent)); } TEST_F(FileUtilTest, DeleteFile) { // Create a file FilePath file_name = temp_dir_.path().Append(FPL("Test DeleteFile 1.txt")); CreateTextFile(file_name, bogus_content); - ASSERT_TRUE(base::PathExists(file_name)); + ASSERT_TRUE(PathExists(file_name)); // Make sure it's deleted - EXPECT_TRUE(base::DeleteFile(file_name, false)); - EXPECT_FALSE(base::PathExists(file_name)); + EXPECT_TRUE(DeleteFile(file_name, false)); + EXPECT_FALSE(PathExists(file_name)); // Test recursive case, create a new file file_name = temp_dir_.path().Append(FPL("Test DeleteFile 2.txt")); CreateTextFile(file_name, bogus_content); - ASSERT_TRUE(base::PathExists(file_name)); + ASSERT_TRUE(PathExists(file_name)); // Make sure it's deleted - EXPECT_TRUE(base::DeleteFile(file_name, true)); - EXPECT_FALSE(base::PathExists(file_name)); + EXPECT_TRUE(DeleteFile(file_name, true)); + EXPECT_FALSE(PathExists(file_name)); } #if defined(OS_POSIX) @@ -775,46 +668,46 @@ TEST_F(FileUtilTest, DeleteSymlinkToExistentFile) { // Create a file. FilePath file_name = temp_dir_.path().Append(FPL("Test DeleteFile 2.txt")); CreateTextFile(file_name, bogus_content); - ASSERT_TRUE(base::PathExists(file_name)); + ASSERT_TRUE(PathExists(file_name)); // Create a symlink to the file. FilePath file_link = temp_dir_.path().Append("file_link_2"); - ASSERT_TRUE(file_util::CreateSymbolicLink(file_name, file_link)) + ASSERT_TRUE(CreateSymbolicLink(file_name, file_link)) << "Failed to create symlink."; // Delete the symbolic link. - EXPECT_TRUE(base::DeleteFile(file_link, false)); + EXPECT_TRUE(DeleteFile(file_link, false)); // Make sure original file is not deleted. - EXPECT_FALSE(base::PathExists(file_link)); - EXPECT_TRUE(base::PathExists(file_name)); + EXPECT_FALSE(PathExists(file_link)); + EXPECT_TRUE(PathExists(file_name)); } TEST_F(FileUtilTest, DeleteSymlinkToNonExistentFile) { // Create a non-existent file path. FilePath non_existent = temp_dir_.path().Append(FPL("Test DeleteFile 3.txt")); - EXPECT_FALSE(base::PathExists(non_existent)); + EXPECT_FALSE(PathExists(non_existent)); // Create a symlink to the non-existent file. FilePath file_link = temp_dir_.path().Append("file_link_3"); - ASSERT_TRUE(file_util::CreateSymbolicLink(non_existent, file_link)) + ASSERT_TRUE(CreateSymbolicLink(non_existent, file_link)) << "Failed to create symlink."; // Make sure the symbolic link is exist. - EXPECT_TRUE(file_util::IsLink(file_link)); - EXPECT_FALSE(base::PathExists(file_link)); + EXPECT_TRUE(IsLink(file_link)); + EXPECT_FALSE(PathExists(file_link)); // Delete the symbolic link. - EXPECT_TRUE(base::DeleteFile(file_link, false)); + EXPECT_TRUE(DeleteFile(file_link, false)); // Make sure the symbolic link is deleted. - EXPECT_FALSE(file_util::IsLink(file_link)); + EXPECT_FALSE(IsLink(file_link)); } TEST_F(FileUtilTest, ChangeFilePermissionsAndRead) { // Create a file path. FilePath file_name = temp_dir_.path().Append(FPL("Test Readable File.txt")); - EXPECT_FALSE(base::PathExists(file_name)); + EXPECT_FALSE(PathExists(file_name)); const std::string kData("hello"); @@ -824,33 +717,31 @@ TEST_F(FileUtilTest, ChangeFilePermissionsAndRead) { // Write file. EXPECT_EQ(static_cast<int>(kData.length()), file_util::WriteFile(file_name, kData.data(), kData.length())); - EXPECT_TRUE(base::PathExists(file_name)); + EXPECT_TRUE(PathExists(file_name)); // Make sure the file is readable. int32 mode = 0; - EXPECT_TRUE(file_util::GetPosixFilePermissions(file_name, &mode)); - EXPECT_TRUE(mode & file_util::FILE_PERMISSION_READ_BY_USER); + EXPECT_TRUE(GetPosixFilePermissions(file_name, &mode)); + EXPECT_TRUE(mode & FILE_PERMISSION_READ_BY_USER); // Get rid of the read permission. - EXPECT_TRUE(file_util::SetPosixFilePermissions(file_name, 0u)); - EXPECT_TRUE(file_util::GetPosixFilePermissions(file_name, &mode)); - EXPECT_FALSE(mode & file_util::FILE_PERMISSION_READ_BY_USER); + EXPECT_TRUE(SetPosixFilePermissions(file_name, 0u)); + EXPECT_TRUE(GetPosixFilePermissions(file_name, &mode)); + EXPECT_FALSE(mode & FILE_PERMISSION_READ_BY_USER); // Make sure the file can't be read. - EXPECT_EQ(-1, file_util::ReadFile(file_name, buffer, buffer_size)); + EXPECT_EQ(-1, ReadFile(file_name, buffer, buffer_size)); // Give the read permission. - EXPECT_TRUE(file_util::SetPosixFilePermissions( - file_name, - file_util::FILE_PERMISSION_READ_BY_USER)); - EXPECT_TRUE(file_util::GetPosixFilePermissions(file_name, &mode)); - EXPECT_TRUE(mode & file_util::FILE_PERMISSION_READ_BY_USER); + EXPECT_TRUE(SetPosixFilePermissions(file_name, FILE_PERMISSION_READ_BY_USER)); + EXPECT_TRUE(GetPosixFilePermissions(file_name, &mode)); + EXPECT_TRUE(mode & FILE_PERMISSION_READ_BY_USER); // Make sure the file can be read. EXPECT_EQ(static_cast<int>(kData.length()), - file_util::ReadFile(file_name, buffer, buffer_size)); + ReadFile(file_name, buffer, buffer_size)); // Delete the file. - EXPECT_TRUE(base::DeleteFile(file_name, false)); - EXPECT_FALSE(base::PathExists(file_name)); + EXPECT_TRUE(DeleteFile(file_name, false)); + EXPECT_FALSE(PathExists(file_name)); delete[] buffer; } @@ -858,86 +749,81 @@ TEST_F(FileUtilTest, ChangeFilePermissionsAndRead) { TEST_F(FileUtilTest, ChangeFilePermissionsAndWrite) { // Create a file path. FilePath file_name = temp_dir_.path().Append(FPL("Test Readable File.txt")); - EXPECT_FALSE(base::PathExists(file_name)); + EXPECT_FALSE(PathExists(file_name)); const std::string kData("hello"); // Write file. EXPECT_EQ(static_cast<int>(kData.length()), file_util::WriteFile(file_name, kData.data(), kData.length())); - EXPECT_TRUE(base::PathExists(file_name)); + EXPECT_TRUE(PathExists(file_name)); // Make sure the file is writable. int mode = 0; - EXPECT_TRUE(file_util::GetPosixFilePermissions(file_name, &mode)); - EXPECT_TRUE(mode & file_util::FILE_PERMISSION_WRITE_BY_USER); + EXPECT_TRUE(GetPosixFilePermissions(file_name, &mode)); + EXPECT_TRUE(mode & FILE_PERMISSION_WRITE_BY_USER); EXPECT_TRUE(PathIsWritable(file_name)); // Get rid of the write permission. - EXPECT_TRUE(file_util::SetPosixFilePermissions(file_name, 0u)); - EXPECT_TRUE(file_util::GetPosixFilePermissions(file_name, &mode)); - EXPECT_FALSE(mode & file_util::FILE_PERMISSION_WRITE_BY_USER); + EXPECT_TRUE(SetPosixFilePermissions(file_name, 0u)); + EXPECT_TRUE(GetPosixFilePermissions(file_name, &mode)); + EXPECT_FALSE(mode & FILE_PERMISSION_WRITE_BY_USER); // Make sure the file can't be write. EXPECT_EQ(-1, file_util::WriteFile(file_name, kData.data(), kData.length())); EXPECT_FALSE(PathIsWritable(file_name)); // Give read permission. - EXPECT_TRUE(file_util::SetPosixFilePermissions( - file_name, - file_util::FILE_PERMISSION_WRITE_BY_USER)); - EXPECT_TRUE(file_util::GetPosixFilePermissions(file_name, &mode)); - EXPECT_TRUE(mode & file_util::FILE_PERMISSION_WRITE_BY_USER); + EXPECT_TRUE(SetPosixFilePermissions(file_name, + FILE_PERMISSION_WRITE_BY_USER)); + EXPECT_TRUE(GetPosixFilePermissions(file_name, &mode)); + EXPECT_TRUE(mode & FILE_PERMISSION_WRITE_BY_USER); // Make sure the file can be write. EXPECT_EQ(static_cast<int>(kData.length()), file_util::WriteFile(file_name, kData.data(), kData.length())); EXPECT_TRUE(PathIsWritable(file_name)); // Delete the file. - EXPECT_TRUE(base::DeleteFile(file_name, false)); - EXPECT_FALSE(base::PathExists(file_name)); + EXPECT_TRUE(DeleteFile(file_name, false)); + EXPECT_FALSE(PathExists(file_name)); } TEST_F(FileUtilTest, ChangeDirectoryPermissionsAndEnumerate) { // Create a directory path. FilePath subdir_path = temp_dir_.path().Append(FPL("PermissionTest1")); - file_util::CreateDirectory(subdir_path); - ASSERT_TRUE(base::PathExists(subdir_path)); + CreateDirectory(subdir_path); + ASSERT_TRUE(PathExists(subdir_path)); // Create a dummy file to enumerate. FilePath file_name = subdir_path.Append(FPL("Test Readable File.txt")); - EXPECT_FALSE(base::PathExists(file_name)); + EXPECT_FALSE(PathExists(file_name)); const std::string kData("hello"); EXPECT_EQ(static_cast<int>(kData.length()), file_util::WriteFile(file_name, kData.data(), kData.length())); - EXPECT_TRUE(base::PathExists(file_name)); + EXPECT_TRUE(PathExists(file_name)); // Make sure the directory has the all permissions. int mode = 0; - EXPECT_TRUE(file_util::GetPosixFilePermissions(subdir_path, &mode)); - EXPECT_EQ(file_util::FILE_PERMISSION_USER_MASK, - mode & file_util::FILE_PERMISSION_USER_MASK); + EXPECT_TRUE(GetPosixFilePermissions(subdir_path, &mode)); + EXPECT_EQ(FILE_PERMISSION_USER_MASK, mode & FILE_PERMISSION_USER_MASK); // Get rid of the permissions from the directory. - EXPECT_TRUE(file_util::SetPosixFilePermissions(subdir_path, 0u)); - EXPECT_TRUE(file_util::GetPosixFilePermissions(subdir_path, &mode)); - EXPECT_FALSE(mode & file_util::FILE_PERMISSION_USER_MASK); + EXPECT_TRUE(SetPosixFilePermissions(subdir_path, 0u)); + EXPECT_TRUE(GetPosixFilePermissions(subdir_path, &mode)); + EXPECT_FALSE(mode & FILE_PERMISSION_USER_MASK); // Make sure the file in the directory can't be enumerated. FileEnumerator f1(subdir_path, true, FileEnumerator::FILES); - EXPECT_TRUE(base::PathExists(subdir_path)); + EXPECT_TRUE(PathExists(subdir_path)); FindResultCollector c1(f1); EXPECT_EQ(c1.size(), 0); - EXPECT_FALSE(file_util::GetPosixFilePermissions(file_name, &mode)); + EXPECT_FALSE(GetPosixFilePermissions(file_name, &mode)); // Give the permissions to the directory. - EXPECT_TRUE(file_util::SetPosixFilePermissions( - subdir_path, - file_util::FILE_PERMISSION_USER_MASK)); - EXPECT_TRUE(file_util::GetPosixFilePermissions(subdir_path, &mode)); - EXPECT_EQ(file_util::FILE_PERMISSION_USER_MASK, - mode & file_util::FILE_PERMISSION_USER_MASK); + EXPECT_TRUE(SetPosixFilePermissions(subdir_path, FILE_PERMISSION_USER_MASK)); + EXPECT_TRUE(GetPosixFilePermissions(subdir_path, &mode)); + EXPECT_EQ(FILE_PERMISSION_USER_MASK, mode & FILE_PERMISSION_USER_MASK); // Make sure the file in the directory can be enumerated. FileEnumerator f2(subdir_path, true, FileEnumerator::FILES); @@ -946,8 +832,8 @@ TEST_F(FileUtilTest, ChangeDirectoryPermissionsAndEnumerate) { EXPECT_EQ(c2.size(), 1); // Delete the file. - EXPECT_TRUE(base::DeleteFile(subdir_path, true)); - EXPECT_FALSE(base::PathExists(subdir_path)); + EXPECT_TRUE(DeleteFile(subdir_path, true)); + EXPECT_FALSE(PathExists(subdir_path)); } #endif // defined(OS_POSIX) @@ -960,25 +846,25 @@ TEST_F(FileUtilTest, DeleteWildCard) { // Create a file and a directory FilePath file_name = temp_dir_.path().Append(FPL("Test DeleteWildCard.txt")); CreateTextFile(file_name, bogus_content); - ASSERT_TRUE(base::PathExists(file_name)); + ASSERT_TRUE(PathExists(file_name)); FilePath subdir_path = temp_dir_.path().Append(FPL("DeleteWildCardDir")); - file_util::CreateDirectory(subdir_path); - ASSERT_TRUE(base::PathExists(subdir_path)); + CreateDirectory(subdir_path); + ASSERT_TRUE(PathExists(subdir_path)); // Create the wildcard path FilePath directory_contents = temp_dir_.path(); directory_contents = directory_contents.Append(FPL("*")); // Delete non-recursively and check that only the file is deleted - EXPECT_TRUE(base::DeleteFile(directory_contents, false)); - EXPECT_FALSE(base::PathExists(file_name)); - EXPECT_TRUE(base::PathExists(subdir_path)); + EXPECT_TRUE(DeleteFile(directory_contents, false)); + EXPECT_FALSE(PathExists(file_name)); + EXPECT_TRUE(PathExists(subdir_path)); // Delete recursively and make sure all contents are deleted - EXPECT_TRUE(base::DeleteFile(directory_contents, true)); - EXPECT_FALSE(base::PathExists(file_name)); - EXPECT_FALSE(base::PathExists(subdir_path)); + EXPECT_TRUE(DeleteFile(directory_contents, true)); + EXPECT_FALSE(PathExists(file_name)); + EXPECT_FALSE(PathExists(subdir_path)); } // TODO(erikkay): see if anyone's actually using this feature of the API @@ -986,20 +872,20 @@ TEST_F(FileUtilTest, DeleteNonExistantWildCard) { // Create a file and a directory FilePath subdir_path = temp_dir_.path().Append(FPL("DeleteNonExistantWildCard")); - file_util::CreateDirectory(subdir_path); - ASSERT_TRUE(base::PathExists(subdir_path)); + CreateDirectory(subdir_path); + ASSERT_TRUE(PathExists(subdir_path)); // Create the wildcard path FilePath directory_contents = subdir_path; directory_contents = directory_contents.Append(FPL("*")); // Delete non-recursively and check nothing got deleted - EXPECT_TRUE(base::DeleteFile(directory_contents, false)); - EXPECT_TRUE(base::PathExists(subdir_path)); + EXPECT_TRUE(DeleteFile(directory_contents, false)); + EXPECT_TRUE(PathExists(subdir_path)); // Delete recursively and check nothing got deleted - EXPECT_TRUE(base::DeleteFile(directory_contents, true)); - EXPECT_TRUE(base::PathExists(subdir_path)); + EXPECT_TRUE(DeleteFile(directory_contents, true)); + EXPECT_TRUE(PathExists(subdir_path)); } #endif @@ -1007,60 +893,60 @@ TEST_F(FileUtilTest, DeleteNonExistantWildCard) { TEST_F(FileUtilTest, DeleteDirNonRecursive) { // Create a subdirectory and put a file and two directories inside. FilePath test_subdir = temp_dir_.path().Append(FPL("DeleteDirNonRecursive")); - file_util::CreateDirectory(test_subdir); - ASSERT_TRUE(base::PathExists(test_subdir)); + CreateDirectory(test_subdir); + ASSERT_TRUE(PathExists(test_subdir)); FilePath file_name = test_subdir.Append(FPL("Test DeleteDir.txt")); CreateTextFile(file_name, bogus_content); - ASSERT_TRUE(base::PathExists(file_name)); + ASSERT_TRUE(PathExists(file_name)); FilePath subdir_path1 = test_subdir.Append(FPL("TestSubDir1")); - file_util::CreateDirectory(subdir_path1); - ASSERT_TRUE(base::PathExists(subdir_path1)); + CreateDirectory(subdir_path1); + ASSERT_TRUE(PathExists(subdir_path1)); FilePath subdir_path2 = test_subdir.Append(FPL("TestSubDir2")); - file_util::CreateDirectory(subdir_path2); - ASSERT_TRUE(base::PathExists(subdir_path2)); + CreateDirectory(subdir_path2); + ASSERT_TRUE(PathExists(subdir_path2)); // Delete non-recursively and check that the empty dir got deleted - EXPECT_TRUE(base::DeleteFile(subdir_path2, false)); - EXPECT_FALSE(base::PathExists(subdir_path2)); + EXPECT_TRUE(DeleteFile(subdir_path2, false)); + EXPECT_FALSE(PathExists(subdir_path2)); // Delete non-recursively and check that nothing got deleted - EXPECT_FALSE(base::DeleteFile(test_subdir, false)); - EXPECT_TRUE(base::PathExists(test_subdir)); - EXPECT_TRUE(base::PathExists(file_name)); - EXPECT_TRUE(base::PathExists(subdir_path1)); + EXPECT_FALSE(DeleteFile(test_subdir, false)); + EXPECT_TRUE(PathExists(test_subdir)); + EXPECT_TRUE(PathExists(file_name)); + EXPECT_TRUE(PathExists(subdir_path1)); } // Tests recursive Delete() for a directory. TEST_F(FileUtilTest, DeleteDirRecursive) { // Create a subdirectory and put a file and two directories inside. FilePath test_subdir = temp_dir_.path().Append(FPL("DeleteDirRecursive")); - file_util::CreateDirectory(test_subdir); - ASSERT_TRUE(base::PathExists(test_subdir)); + CreateDirectory(test_subdir); + ASSERT_TRUE(PathExists(test_subdir)); FilePath file_name = test_subdir.Append(FPL("Test DeleteDirRecursive.txt")); CreateTextFile(file_name, bogus_content); - ASSERT_TRUE(base::PathExists(file_name)); + ASSERT_TRUE(PathExists(file_name)); FilePath subdir_path1 = test_subdir.Append(FPL("TestSubDir1")); - file_util::CreateDirectory(subdir_path1); - ASSERT_TRUE(base::PathExists(subdir_path1)); + CreateDirectory(subdir_path1); + ASSERT_TRUE(PathExists(subdir_path1)); FilePath subdir_path2 = test_subdir.Append(FPL("TestSubDir2")); - file_util::CreateDirectory(subdir_path2); - ASSERT_TRUE(base::PathExists(subdir_path2)); + CreateDirectory(subdir_path2); + ASSERT_TRUE(PathExists(subdir_path2)); // Delete recursively and check that the empty dir got deleted - EXPECT_TRUE(base::DeleteFile(subdir_path2, true)); - EXPECT_FALSE(base::PathExists(subdir_path2)); + EXPECT_TRUE(DeleteFile(subdir_path2, true)); + EXPECT_FALSE(PathExists(subdir_path2)); // Delete recursively and check that everything got deleted - EXPECT_TRUE(base::DeleteFile(test_subdir, true)); - EXPECT_FALSE(base::PathExists(file_name)); - EXPECT_FALSE(base::PathExists(subdir_path1)); - EXPECT_FALSE(base::PathExists(test_subdir)); + EXPECT_TRUE(DeleteFile(test_subdir, true)); + EXPECT_FALSE(PathExists(file_name)); + EXPECT_FALSE(PathExists(subdir_path1)); + EXPECT_FALSE(PathExists(test_subdir)); } TEST_F(FileUtilTest, MoveFileNew) { @@ -1068,18 +954,18 @@ TEST_F(FileUtilTest, MoveFileNew) { FilePath file_name_from = temp_dir_.path().Append(FILE_PATH_LITERAL("Move_Test_File.txt")); CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); - ASSERT_TRUE(base::PathExists(file_name_from)); + ASSERT_TRUE(PathExists(file_name_from)); // The destination. FilePath file_name_to = temp_dir_.path().Append( FILE_PATH_LITERAL("Move_Test_File_Destination.txt")); - ASSERT_FALSE(base::PathExists(file_name_to)); + ASSERT_FALSE(PathExists(file_name_to)); - EXPECT_TRUE(base::Move(file_name_from, file_name_to)); + EXPECT_TRUE(Move(file_name_from, file_name_to)); // Check everything has been moved. - EXPECT_FALSE(base::PathExists(file_name_from)); - EXPECT_TRUE(base::PathExists(file_name_to)); + EXPECT_FALSE(PathExists(file_name_from)); + EXPECT_TRUE(PathExists(file_name_to)); } TEST_F(FileUtilTest, MoveFileExists) { @@ -1087,19 +973,19 @@ TEST_F(FileUtilTest, MoveFileExists) { FilePath file_name_from = temp_dir_.path().Append(FILE_PATH_LITERAL("Move_Test_File.txt")); CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); - ASSERT_TRUE(base::PathExists(file_name_from)); + ASSERT_TRUE(PathExists(file_name_from)); // The destination name. FilePath file_name_to = temp_dir_.path().Append( FILE_PATH_LITERAL("Move_Test_File_Destination.txt")); CreateTextFile(file_name_to, L"Old file content"); - ASSERT_TRUE(base::PathExists(file_name_to)); + ASSERT_TRUE(PathExists(file_name_to)); - EXPECT_TRUE(base::Move(file_name_from, file_name_to)); + EXPECT_TRUE(Move(file_name_from, file_name_to)); // Check everything has been moved. - EXPECT_FALSE(base::PathExists(file_name_from)); - EXPECT_TRUE(base::PathExists(file_name_to)); + EXPECT_FALSE(PathExists(file_name_from)); + EXPECT_TRUE(PathExists(file_name_to)); EXPECT_TRUE(L"Gooooooooooooooooooooogle" == ReadTextFile(file_name_to)); } @@ -1108,15 +994,15 @@ TEST_F(FileUtilTest, MoveFileDirExists) { FilePath file_name_from = temp_dir_.path().Append(FILE_PATH_LITERAL("Move_Test_File.txt")); CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); - ASSERT_TRUE(base::PathExists(file_name_from)); + ASSERT_TRUE(PathExists(file_name_from)); // The destination directory FilePath dir_name_to = temp_dir_.path().Append(FILE_PATH_LITERAL("Destination")); - file_util::CreateDirectory(dir_name_to); - ASSERT_TRUE(base::PathExists(dir_name_to)); + CreateDirectory(dir_name_to); + ASSERT_TRUE(PathExists(dir_name_to)); - EXPECT_FALSE(base::Move(file_name_from, dir_name_to)); + EXPECT_FALSE(Move(file_name_from, dir_name_to)); } @@ -1124,14 +1010,14 @@ TEST_F(FileUtilTest, MoveNew) { // Create a directory FilePath dir_name_from = temp_dir_.path().Append(FILE_PATH_LITERAL("Move_From_Subdir")); - file_util::CreateDirectory(dir_name_from); - ASSERT_TRUE(base::PathExists(dir_name_from)); + CreateDirectory(dir_name_from); + ASSERT_TRUE(PathExists(dir_name_from)); // Create a file under the directory FilePath txt_file_name(FILE_PATH_LITERAL("Move_Test_File.txt")); FilePath file_name_from = dir_name_from.Append(txt_file_name); CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); - ASSERT_TRUE(base::PathExists(file_name_from)); + ASSERT_TRUE(PathExists(file_name_from)); // Move the directory. FilePath dir_name_to = @@ -1139,40 +1025,40 @@ TEST_F(FileUtilTest, MoveNew) { FilePath file_name_to = dir_name_to.Append(FILE_PATH_LITERAL("Move_Test_File.txt")); - ASSERT_FALSE(base::PathExists(dir_name_to)); + ASSERT_FALSE(PathExists(dir_name_to)); - EXPECT_TRUE(base::Move(dir_name_from, dir_name_to)); + EXPECT_TRUE(Move(dir_name_from, dir_name_to)); // Check everything has been moved. - EXPECT_FALSE(base::PathExists(dir_name_from)); - EXPECT_FALSE(base::PathExists(file_name_from)); - EXPECT_TRUE(base::PathExists(dir_name_to)); - EXPECT_TRUE(base::PathExists(file_name_to)); + EXPECT_FALSE(PathExists(dir_name_from)); + EXPECT_FALSE(PathExists(file_name_from)); + EXPECT_TRUE(PathExists(dir_name_to)); + EXPECT_TRUE(PathExists(file_name_to)); // Test path traversal. file_name_from = dir_name_to.Append(txt_file_name); file_name_to = dir_name_to.Append(FILE_PATH_LITERAL("..")); file_name_to = file_name_to.Append(txt_file_name); - EXPECT_FALSE(base::Move(file_name_from, file_name_to)); - EXPECT_TRUE(base::PathExists(file_name_from)); - EXPECT_FALSE(base::PathExists(file_name_to)); - EXPECT_TRUE(base::internal::MoveUnsafe(file_name_from, file_name_to)); - EXPECT_FALSE(base::PathExists(file_name_from)); - EXPECT_TRUE(base::PathExists(file_name_to)); + EXPECT_FALSE(Move(file_name_from, file_name_to)); + EXPECT_TRUE(PathExists(file_name_from)); + EXPECT_FALSE(PathExists(file_name_to)); + EXPECT_TRUE(internal::MoveUnsafe(file_name_from, file_name_to)); + EXPECT_FALSE(PathExists(file_name_from)); + EXPECT_TRUE(PathExists(file_name_to)); } TEST_F(FileUtilTest, MoveExist) { // Create a directory FilePath dir_name_from = temp_dir_.path().Append(FILE_PATH_LITERAL("Move_From_Subdir")); - file_util::CreateDirectory(dir_name_from); - ASSERT_TRUE(base::PathExists(dir_name_from)); + CreateDirectory(dir_name_from); + ASSERT_TRUE(PathExists(dir_name_from)); // Create a file under the directory FilePath file_name_from = dir_name_from.Append(FILE_PATH_LITERAL("Move_Test_File.txt")); CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); - ASSERT_TRUE(base::PathExists(file_name_from)); + ASSERT_TRUE(PathExists(file_name_from)); // Move the directory FilePath dir_name_exists = @@ -1184,42 +1070,42 @@ TEST_F(FileUtilTest, MoveExist) { dir_name_to.Append(FILE_PATH_LITERAL("Move_Test_File.txt")); // Create the destination directory. - file_util::CreateDirectory(dir_name_exists); - ASSERT_TRUE(base::PathExists(dir_name_exists)); + CreateDirectory(dir_name_exists); + ASSERT_TRUE(PathExists(dir_name_exists)); - EXPECT_TRUE(base::Move(dir_name_from, dir_name_to)); + EXPECT_TRUE(Move(dir_name_from, dir_name_to)); // Check everything has been moved. - EXPECT_FALSE(base::PathExists(dir_name_from)); - EXPECT_FALSE(base::PathExists(file_name_from)); - EXPECT_TRUE(base::PathExists(dir_name_to)); - EXPECT_TRUE(base::PathExists(file_name_to)); + EXPECT_FALSE(PathExists(dir_name_from)); + EXPECT_FALSE(PathExists(file_name_from)); + EXPECT_TRUE(PathExists(dir_name_to)); + EXPECT_TRUE(PathExists(file_name_to)); } TEST_F(FileUtilTest, CopyDirectoryRecursivelyNew) { // Create a directory. FilePath dir_name_from = temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_From_Subdir")); - file_util::CreateDirectory(dir_name_from); - ASSERT_TRUE(base::PathExists(dir_name_from)); + CreateDirectory(dir_name_from); + ASSERT_TRUE(PathExists(dir_name_from)); // Create a file under the directory. FilePath file_name_from = dir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); - ASSERT_TRUE(base::PathExists(file_name_from)); + ASSERT_TRUE(PathExists(file_name_from)); // Create a subdirectory. FilePath subdir_name_from = dir_name_from.Append(FILE_PATH_LITERAL("Subdir")); - file_util::CreateDirectory(subdir_name_from); - ASSERT_TRUE(base::PathExists(subdir_name_from)); + CreateDirectory(subdir_name_from); + ASSERT_TRUE(PathExists(subdir_name_from)); // Create a file under the subdirectory. FilePath file_name2_from = subdir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); CreateTextFile(file_name2_from, L"Gooooooooooooooooooooogle"); - ASSERT_TRUE(base::PathExists(file_name2_from)); + ASSERT_TRUE(PathExists(file_name2_from)); // Copy the directory recursively. FilePath dir_name_to = @@ -1231,45 +1117,45 @@ TEST_F(FileUtilTest, CopyDirectoryRecursivelyNew) { FilePath file_name2_to = subdir_name_to.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); - ASSERT_FALSE(base::PathExists(dir_name_to)); + ASSERT_FALSE(PathExists(dir_name_to)); - EXPECT_TRUE(base::CopyDirectory(dir_name_from, dir_name_to, true)); + EXPECT_TRUE(CopyDirectory(dir_name_from, dir_name_to, true)); // Check everything has been copied. - EXPECT_TRUE(base::PathExists(dir_name_from)); - EXPECT_TRUE(base::PathExists(file_name_from)); - EXPECT_TRUE(base::PathExists(subdir_name_from)); - EXPECT_TRUE(base::PathExists(file_name2_from)); - EXPECT_TRUE(base::PathExists(dir_name_to)); - EXPECT_TRUE(base::PathExists(file_name_to)); - EXPECT_TRUE(base::PathExists(subdir_name_to)); - EXPECT_TRUE(base::PathExists(file_name2_to)); + EXPECT_TRUE(PathExists(dir_name_from)); + EXPECT_TRUE(PathExists(file_name_from)); + EXPECT_TRUE(PathExists(subdir_name_from)); + EXPECT_TRUE(PathExists(file_name2_from)); + EXPECT_TRUE(PathExists(dir_name_to)); + EXPECT_TRUE(PathExists(file_name_to)); + EXPECT_TRUE(PathExists(subdir_name_to)); + EXPECT_TRUE(PathExists(file_name2_to)); } TEST_F(FileUtilTest, CopyDirectoryRecursivelyExists) { // Create a directory. FilePath dir_name_from = temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_From_Subdir")); - file_util::CreateDirectory(dir_name_from); - ASSERT_TRUE(base::PathExists(dir_name_from)); + CreateDirectory(dir_name_from); + ASSERT_TRUE(PathExists(dir_name_from)); // Create a file under the directory. FilePath file_name_from = dir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); - ASSERT_TRUE(base::PathExists(file_name_from)); + ASSERT_TRUE(PathExists(file_name_from)); // Create a subdirectory. FilePath subdir_name_from = dir_name_from.Append(FILE_PATH_LITERAL("Subdir")); - file_util::CreateDirectory(subdir_name_from); - ASSERT_TRUE(base::PathExists(subdir_name_from)); + CreateDirectory(subdir_name_from); + ASSERT_TRUE(PathExists(subdir_name_from)); // Create a file under the subdirectory. FilePath file_name2_from = subdir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); CreateTextFile(file_name2_from, L"Gooooooooooooooooooooogle"); - ASSERT_TRUE(base::PathExists(file_name2_from)); + ASSERT_TRUE(PathExists(file_name2_from)); // Copy the directory recursively. FilePath dir_name_exists = @@ -1285,46 +1171,46 @@ TEST_F(FileUtilTest, CopyDirectoryRecursivelyExists) { subdir_name_to.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); // Create the destination directory. - file_util::CreateDirectory(dir_name_exists); - ASSERT_TRUE(base::PathExists(dir_name_exists)); + CreateDirectory(dir_name_exists); + ASSERT_TRUE(PathExists(dir_name_exists)); - EXPECT_TRUE(base::CopyDirectory(dir_name_from, dir_name_exists, true)); + EXPECT_TRUE(CopyDirectory(dir_name_from, dir_name_exists, true)); // Check everything has been copied. - EXPECT_TRUE(base::PathExists(dir_name_from)); - EXPECT_TRUE(base::PathExists(file_name_from)); - EXPECT_TRUE(base::PathExists(subdir_name_from)); - EXPECT_TRUE(base::PathExists(file_name2_from)); - EXPECT_TRUE(base::PathExists(dir_name_to)); - EXPECT_TRUE(base::PathExists(file_name_to)); - EXPECT_TRUE(base::PathExists(subdir_name_to)); - EXPECT_TRUE(base::PathExists(file_name2_to)); + EXPECT_TRUE(PathExists(dir_name_from)); + EXPECT_TRUE(PathExists(file_name_from)); + EXPECT_TRUE(PathExists(subdir_name_from)); + EXPECT_TRUE(PathExists(file_name2_from)); + EXPECT_TRUE(PathExists(dir_name_to)); + EXPECT_TRUE(PathExists(file_name_to)); + EXPECT_TRUE(PathExists(subdir_name_to)); + EXPECT_TRUE(PathExists(file_name2_to)); } TEST_F(FileUtilTest, CopyDirectoryNew) { // Create a directory. FilePath dir_name_from = temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_From_Subdir")); - file_util::CreateDirectory(dir_name_from); - ASSERT_TRUE(base::PathExists(dir_name_from)); + CreateDirectory(dir_name_from); + ASSERT_TRUE(PathExists(dir_name_from)); // Create a file under the directory. FilePath file_name_from = dir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); - ASSERT_TRUE(base::PathExists(file_name_from)); + ASSERT_TRUE(PathExists(file_name_from)); // Create a subdirectory. FilePath subdir_name_from = dir_name_from.Append(FILE_PATH_LITERAL("Subdir")); - file_util::CreateDirectory(subdir_name_from); - ASSERT_TRUE(base::PathExists(subdir_name_from)); + CreateDirectory(subdir_name_from); + ASSERT_TRUE(PathExists(subdir_name_from)); // Create a file under the subdirectory. FilePath file_name2_from = subdir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); CreateTextFile(file_name2_from, L"Gooooooooooooooooooooogle"); - ASSERT_TRUE(base::PathExists(file_name2_from)); + ASSERT_TRUE(PathExists(file_name2_from)); // Copy the directory not recursively. FilePath dir_name_to = @@ -1334,44 +1220,44 @@ TEST_F(FileUtilTest, CopyDirectoryNew) { FilePath subdir_name_to = dir_name_to.Append(FILE_PATH_LITERAL("Subdir")); - ASSERT_FALSE(base::PathExists(dir_name_to)); + ASSERT_FALSE(PathExists(dir_name_to)); - EXPECT_TRUE(base::CopyDirectory(dir_name_from, dir_name_to, false)); + EXPECT_TRUE(CopyDirectory(dir_name_from, dir_name_to, false)); // Check everything has been copied. - EXPECT_TRUE(base::PathExists(dir_name_from)); - EXPECT_TRUE(base::PathExists(file_name_from)); - EXPECT_TRUE(base::PathExists(subdir_name_from)); - EXPECT_TRUE(base::PathExists(file_name2_from)); - EXPECT_TRUE(base::PathExists(dir_name_to)); - EXPECT_TRUE(base::PathExists(file_name_to)); - EXPECT_FALSE(base::PathExists(subdir_name_to)); + EXPECT_TRUE(PathExists(dir_name_from)); + EXPECT_TRUE(PathExists(file_name_from)); + EXPECT_TRUE(PathExists(subdir_name_from)); + EXPECT_TRUE(PathExists(file_name2_from)); + EXPECT_TRUE(PathExists(dir_name_to)); + EXPECT_TRUE(PathExists(file_name_to)); + EXPECT_FALSE(PathExists(subdir_name_to)); } TEST_F(FileUtilTest, CopyDirectoryExists) { // Create a directory. FilePath dir_name_from = temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_From_Subdir")); - file_util::CreateDirectory(dir_name_from); - ASSERT_TRUE(base::PathExists(dir_name_from)); + CreateDirectory(dir_name_from); + ASSERT_TRUE(PathExists(dir_name_from)); // Create a file under the directory. FilePath file_name_from = dir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); - ASSERT_TRUE(base::PathExists(file_name_from)); + ASSERT_TRUE(PathExists(file_name_from)); // Create a subdirectory. FilePath subdir_name_from = dir_name_from.Append(FILE_PATH_LITERAL("Subdir")); - file_util::CreateDirectory(subdir_name_from); - ASSERT_TRUE(base::PathExists(subdir_name_from)); + CreateDirectory(subdir_name_from); + ASSERT_TRUE(PathExists(subdir_name_from)); // Create a file under the subdirectory. FilePath file_name2_from = subdir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); CreateTextFile(file_name2_from, L"Gooooooooooooooooooooogle"); - ASSERT_TRUE(base::PathExists(file_name2_from)); + ASSERT_TRUE(PathExists(file_name2_from)); // Copy the directory not recursively. FilePath dir_name_to = @@ -1382,19 +1268,19 @@ TEST_F(FileUtilTest, CopyDirectoryExists) { dir_name_to.Append(FILE_PATH_LITERAL("Subdir")); // Create the destination directory. - file_util::CreateDirectory(dir_name_to); - ASSERT_TRUE(base::PathExists(dir_name_to)); + CreateDirectory(dir_name_to); + ASSERT_TRUE(PathExists(dir_name_to)); - EXPECT_TRUE(base::CopyDirectory(dir_name_from, dir_name_to, false)); + EXPECT_TRUE(CopyDirectory(dir_name_from, dir_name_to, false)); // Check everything has been copied. - EXPECT_TRUE(base::PathExists(dir_name_from)); - EXPECT_TRUE(base::PathExists(file_name_from)); - EXPECT_TRUE(base::PathExists(subdir_name_from)); - EXPECT_TRUE(base::PathExists(file_name2_from)); - EXPECT_TRUE(base::PathExists(dir_name_to)); - EXPECT_TRUE(base::PathExists(file_name_to)); - EXPECT_FALSE(base::PathExists(subdir_name_to)); + EXPECT_TRUE(PathExists(dir_name_from)); + EXPECT_TRUE(PathExists(file_name_from)); + EXPECT_TRUE(PathExists(subdir_name_from)); + EXPECT_TRUE(PathExists(file_name2_from)); + EXPECT_TRUE(PathExists(dir_name_to)); + EXPECT_TRUE(PathExists(file_name_to)); + EXPECT_FALSE(PathExists(subdir_name_to)); } TEST_F(FileUtilTest, CopyFileWithCopyDirectoryRecursiveToNew) { @@ -1402,17 +1288,17 @@ TEST_F(FileUtilTest, CopyFileWithCopyDirectoryRecursiveToNew) { FilePath file_name_from = temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); - ASSERT_TRUE(base::PathExists(file_name_from)); + ASSERT_TRUE(PathExists(file_name_from)); // The destination name FilePath file_name_to = temp_dir_.path().Append( FILE_PATH_LITERAL("Copy_Test_File_Destination.txt")); - ASSERT_FALSE(base::PathExists(file_name_to)); + ASSERT_FALSE(PathExists(file_name_to)); - EXPECT_TRUE(base::CopyDirectory(file_name_from, file_name_to, true)); + EXPECT_TRUE(CopyDirectory(file_name_from, file_name_to, true)); // Check the has been copied - EXPECT_TRUE(base::PathExists(file_name_to)); + EXPECT_TRUE(PathExists(file_name_to)); } TEST_F(FileUtilTest, CopyFileWithCopyDirectoryRecursiveToExisting) { @@ -1420,18 +1306,18 @@ TEST_F(FileUtilTest, CopyFileWithCopyDirectoryRecursiveToExisting) { FilePath file_name_from = temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); - ASSERT_TRUE(base::PathExists(file_name_from)); + ASSERT_TRUE(PathExists(file_name_from)); // The destination name FilePath file_name_to = temp_dir_.path().Append( FILE_PATH_LITERAL("Copy_Test_File_Destination.txt")); CreateTextFile(file_name_to, L"Old file content"); - ASSERT_TRUE(base::PathExists(file_name_to)); + ASSERT_TRUE(PathExists(file_name_to)); - EXPECT_TRUE(base::CopyDirectory(file_name_from, file_name_to, true)); + EXPECT_TRUE(CopyDirectory(file_name_from, file_name_to, true)); // Check the has been copied - EXPECT_TRUE(base::PathExists(file_name_to)); + EXPECT_TRUE(PathExists(file_name_to)); EXPECT_TRUE(L"Gooooooooooooooooooooogle" == ReadTextFile(file_name_to)); } @@ -1440,34 +1326,34 @@ TEST_F(FileUtilTest, CopyFileWithCopyDirectoryRecursiveToExistingDirectory) { FilePath file_name_from = temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); - ASSERT_TRUE(base::PathExists(file_name_from)); + ASSERT_TRUE(PathExists(file_name_from)); // The destination FilePath dir_name_to = temp_dir_.path().Append(FILE_PATH_LITERAL("Destination")); - file_util::CreateDirectory(dir_name_to); - ASSERT_TRUE(base::PathExists(dir_name_to)); + CreateDirectory(dir_name_to); + ASSERT_TRUE(PathExists(dir_name_to)); FilePath file_name_to = dir_name_to.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); - EXPECT_TRUE(base::CopyDirectory(file_name_from, dir_name_to, true)); + EXPECT_TRUE(CopyDirectory(file_name_from, dir_name_to, true)); // Check the has been copied - EXPECT_TRUE(base::PathExists(file_name_to)); + EXPECT_TRUE(PathExists(file_name_to)); } TEST_F(FileUtilTest, CopyDirectoryWithTrailingSeparators) { // Create a directory. FilePath dir_name_from = temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_From_Subdir")); - file_util::CreateDirectory(dir_name_from); - ASSERT_TRUE(base::PathExists(dir_name_from)); + CreateDirectory(dir_name_from); + ASSERT_TRUE(PathExists(dir_name_from)); // Create a file under the directory. FilePath file_name_from = dir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); - ASSERT_TRUE(base::PathExists(file_name_from)); + ASSERT_TRUE(PathExists(file_name_from)); // Copy the directory recursively. FilePath dir_name_to = @@ -1484,51 +1370,51 @@ TEST_F(FileUtilTest, CopyDirectoryWithTrailingSeparators) { temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_From_Subdir///")); #endif - EXPECT_TRUE(base::CopyDirectory(from_path, dir_name_to, true)); + EXPECT_TRUE(CopyDirectory(from_path, dir_name_to, true)); // Check everything has been copied. - EXPECT_TRUE(base::PathExists(dir_name_from)); - EXPECT_TRUE(base::PathExists(file_name_from)); - EXPECT_TRUE(base::PathExists(dir_name_to)); - EXPECT_TRUE(base::PathExists(file_name_to)); + EXPECT_TRUE(PathExists(dir_name_from)); + EXPECT_TRUE(PathExists(file_name_from)); + EXPECT_TRUE(PathExists(dir_name_to)); + EXPECT_TRUE(PathExists(file_name_to)); } TEST_F(FileUtilTest, CopyFile) { // Create a directory FilePath dir_name_from = temp_dir_.path().Append(FILE_PATH_LITERAL("Copy_From_Subdir")); - file_util::CreateDirectory(dir_name_from); - ASSERT_TRUE(base::PathExists(dir_name_from)); + CreateDirectory(dir_name_from); + ASSERT_TRUE(PathExists(dir_name_from)); // Create a file under the directory FilePath file_name_from = dir_name_from.Append(FILE_PATH_LITERAL("Copy_Test_File.txt")); const std::wstring file_contents(L"Gooooooooooooooooooooogle"); CreateTextFile(file_name_from, file_contents); - ASSERT_TRUE(base::PathExists(file_name_from)); + ASSERT_TRUE(PathExists(file_name_from)); // Copy the file. FilePath dest_file = dir_name_from.Append(FILE_PATH_LITERAL("DestFile.txt")); - ASSERT_TRUE(base::CopyFile(file_name_from, dest_file)); + ASSERT_TRUE(CopyFile(file_name_from, dest_file)); // Copy the file to another location using '..' in the path. FilePath dest_file2(dir_name_from); dest_file2 = dest_file2.AppendASCII(".."); dest_file2 = dest_file2.AppendASCII("DestFile.txt"); - ASSERT_FALSE(base::CopyFile(file_name_from, dest_file2)); - ASSERT_TRUE(base::internal::CopyFileUnsafe(file_name_from, dest_file2)); + ASSERT_FALSE(CopyFile(file_name_from, dest_file2)); + ASSERT_TRUE(internal::CopyFileUnsafe(file_name_from, dest_file2)); FilePath dest_file2_test(dir_name_from); dest_file2_test = dest_file2_test.DirName(); dest_file2_test = dest_file2_test.AppendASCII("DestFile.txt"); // Check everything has been copied. - EXPECT_TRUE(base::PathExists(file_name_from)); - EXPECT_TRUE(base::PathExists(dest_file)); + EXPECT_TRUE(PathExists(file_name_from)); + EXPECT_TRUE(PathExists(dest_file)); const std::wstring read_contents = ReadTextFile(dest_file); EXPECT_EQ(file_contents, read_contents); - EXPECT_TRUE(base::PathExists(dest_file2_test)); - EXPECT_TRUE(base::PathExists(dest_file2)); + EXPECT_TRUE(PathExists(dest_file2_test)); + EXPECT_TRUE(PathExists(dest_file2)); } // file_util winds up using autoreleased objects on the Mac, so this needs @@ -1537,9 +1423,9 @@ typedef PlatformTest ReadOnlyFileUtilTest; TEST_F(ReadOnlyFileUtilTest, ContentsEqual) { FilePath data_dir; - ASSERT_TRUE(PathService::Get(base::DIR_TEST_DATA, &data_dir)); + ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &data_dir)); data_dir = data_dir.AppendASCII("file_util"); - ASSERT_TRUE(base::PathExists(data_dir)); + ASSERT_TRUE(PathExists(data_dir)); FilePath original_file = data_dir.Append(FILE_PATH_LITERAL("original.txt")); @@ -1583,9 +1469,9 @@ TEST_F(ReadOnlyFileUtilTest, ContentsEqual) { TEST_F(ReadOnlyFileUtilTest, TextContentsEqual) { FilePath data_dir; - ASSERT_TRUE(PathService::Get(base::DIR_TEST_DATA, &data_dir)); + ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &data_dir)); data_dir = data_dir.AppendASCII("file_util"); - ASSERT_TRUE(base::PathExists(data_dir)); + ASSERT_TRUE(PathExists(data_dir)); FilePath original_file = data_dir.Append(FILE_PATH_LITERAL("original.txt")); @@ -1632,14 +1518,14 @@ TEST_F(FileUtilTest, CopyAndDeleteDirectoryTest) { // Create a directory FilePath dir_name_from = temp_dir_.path().Append(FILE_PATH_LITERAL("CopyAndDelete_From_Subdir")); - file_util::CreateDirectory(dir_name_from); - ASSERT_TRUE(base::PathExists(dir_name_from)); + CreateDirectory(dir_name_from); + ASSERT_TRUE(PathExists(dir_name_from)); // Create a file under the directory FilePath file_name_from = dir_name_from.Append(FILE_PATH_LITERAL("CopyAndDelete_Test_File.txt")); CreateTextFile(file_name_from, L"Gooooooooooooooooooooogle"); - ASSERT_TRUE(base::PathExists(file_name_from)); + ASSERT_TRUE(PathExists(file_name_from)); // Move the directory by using CopyAndDeleteDirectory FilePath dir_name_to = temp_dir_.path().Append( @@ -1647,16 +1533,16 @@ TEST_F(FileUtilTest, CopyAndDeleteDirectoryTest) { FilePath file_name_to = dir_name_to.Append(FILE_PATH_LITERAL("CopyAndDelete_Test_File.txt")); - ASSERT_FALSE(base::PathExists(dir_name_to)); + ASSERT_FALSE(PathExists(dir_name_to)); - EXPECT_TRUE(base::internal::CopyAndDeleteDirectory(dir_name_from, + EXPECT_TRUE(internal::CopyAndDeleteDirectory(dir_name_from, dir_name_to)); // Check everything has been moved. - EXPECT_FALSE(base::PathExists(dir_name_from)); - EXPECT_FALSE(base::PathExists(file_name_from)); - EXPECT_TRUE(base::PathExists(dir_name_to)); - EXPECT_TRUE(base::PathExists(file_name_to)); + EXPECT_FALSE(PathExists(dir_name_from)); + EXPECT_FALSE(PathExists(file_name_from)); + EXPECT_TRUE(PathExists(dir_name_to)); + EXPECT_TRUE(PathExists(file_name_to)); } TEST_F(FileUtilTest, GetTempDirTest) { @@ -1673,7 +1559,7 @@ TEST_F(FileUtilTest, GetTempDirTest) { for (unsigned int i = 0; i < arraysize(kTmpValues); ++i) { FilePath path; ::_tputenv_s(kTmpKey, kTmpValues[i]); - file_util::GetTempDir(&path); + GetTempDir(&path); EXPECT_TRUE(path.IsAbsolute()) << "$TMP=" << kTmpValues[i] << " result=" << path.value(); } @@ -1691,14 +1577,14 @@ TEST_F(FileUtilTest, GetTempDirTest) { TEST_F(FileUtilTest, CreateTemporaryFileTest) { FilePath temp_files[3]; for (int i = 0; i < 3; i++) { - ASSERT_TRUE(file_util::CreateTemporaryFile(&(temp_files[i]))); - EXPECT_TRUE(base::PathExists(temp_files[i])); + ASSERT_TRUE(CreateTemporaryFile(&(temp_files[i]))); + EXPECT_TRUE(PathExists(temp_files[i])); EXPECT_FALSE(DirectoryExists(temp_files[i])); } for (int i = 0; i < 3; i++) EXPECT_FALSE(temp_files[i] == temp_files[(i+1)%3]); for (int i = 0; i < 3; i++) - EXPECT_TRUE(base::DeleteFile(temp_files[i], false)); + EXPECT_TRUE(DeleteFile(temp_files[i], false)); } TEST_F(FileUtilTest, CreateAndOpenTemporaryFileTest) { @@ -1708,9 +1594,9 @@ TEST_F(FileUtilTest, CreateAndOpenTemporaryFileTest) { // Create; make sure they are open and exist. for (i = 0; i < 3; ++i) { - fps[i] = file_util::CreateAndOpenTemporaryFile(&(names[i])); + fps[i] = CreateAndOpenTemporaryFile(&(names[i])); ASSERT_TRUE(fps[i]); - EXPECT_TRUE(base::PathExists(names[i])); + EXPECT_TRUE(PathExists(names[i])); } // Make sure all names are unique. @@ -1720,33 +1606,32 @@ TEST_F(FileUtilTest, CreateAndOpenTemporaryFileTest) { // Close and delete. for (i = 0; i < 3; ++i) { - EXPECT_TRUE(file_util::CloseFile(fps[i])); - EXPECT_TRUE(base::DeleteFile(names[i], false)); + EXPECT_TRUE(CloseFile(fps[i])); + EXPECT_TRUE(DeleteFile(names[i], false)); } } TEST_F(FileUtilTest, CreateNewTempDirectoryTest) { FilePath temp_dir; - ASSERT_TRUE(file_util::CreateNewTempDirectory(FilePath::StringType(), - &temp_dir)); - EXPECT_TRUE(base::PathExists(temp_dir)); - EXPECT_TRUE(base::DeleteFile(temp_dir, false)); + ASSERT_TRUE(CreateNewTempDirectory(FilePath::StringType(), &temp_dir)); + EXPECT_TRUE(PathExists(temp_dir)); + EXPECT_TRUE(DeleteFile(temp_dir, false)); } TEST_F(FileUtilTest, CreateNewTemporaryDirInDirTest) { FilePath new_dir; - ASSERT_TRUE(file_util::CreateTemporaryDirInDir( + ASSERT_TRUE(CreateTemporaryDirInDir( temp_dir_.path(), FILE_PATH_LITERAL("CreateNewTemporaryDirInDirTest"), &new_dir)); - EXPECT_TRUE(base::PathExists(new_dir)); + EXPECT_TRUE(PathExists(new_dir)); EXPECT_TRUE(temp_dir_.path().IsParent(new_dir)); - EXPECT_TRUE(base::DeleteFile(new_dir, false)); + EXPECT_TRUE(DeleteFile(new_dir, false)); } TEST_F(FileUtilTest, GetShmemTempDirTest) { FilePath dir; - EXPECT_TRUE(file_util::GetShmemTempDir(&dir, false)); + EXPECT_TRUE(GetShmemTempDir(false, &dir)); EXPECT_TRUE(DirectoryExists(dir)); } @@ -1761,22 +1646,22 @@ TEST_F(FileUtilTest, CreateDirectoryTest) { test_root.Append(FILE_PATH_LITERAL("dir/tree/likely/doesnt/exist/")); #endif - EXPECT_FALSE(base::PathExists(test_path)); - EXPECT_TRUE(file_util::CreateDirectory(test_path)); - EXPECT_TRUE(base::PathExists(test_path)); + EXPECT_FALSE(PathExists(test_path)); + EXPECT_TRUE(CreateDirectory(test_path)); + EXPECT_TRUE(PathExists(test_path)); // CreateDirectory returns true if the DirectoryExists returns true. - EXPECT_TRUE(file_util::CreateDirectory(test_path)); + EXPECT_TRUE(CreateDirectory(test_path)); // Doesn't work to create it on top of a non-dir test_path = test_path.Append(FILE_PATH_LITERAL("foobar.txt")); - EXPECT_FALSE(base::PathExists(test_path)); + EXPECT_FALSE(PathExists(test_path)); CreateTextFile(test_path, L"test file"); - EXPECT_TRUE(base::PathExists(test_path)); - EXPECT_FALSE(file_util::CreateDirectory(test_path)); + EXPECT_TRUE(PathExists(test_path)); + EXPECT_FALSE(CreateDirectory(test_path)); - EXPECT_TRUE(base::DeleteFile(test_root, true)); - EXPECT_FALSE(base::PathExists(test_root)); - EXPECT_FALSE(base::PathExists(test_path)); + EXPECT_TRUE(DeleteFile(test_root, true)); + EXPECT_FALSE(PathExists(test_root)); + EXPECT_FALSE(PathExists(test_path)); // Verify assumptions made by the Windows implementation: // 1. The current directory always exists. @@ -1790,16 +1675,16 @@ TEST_F(FileUtilTest, CreateDirectoryTest) { // Given these assumptions hold, it should be safe to // test that "creating" these directories succeeds. - EXPECT_TRUE(file_util::CreateDirectory( + EXPECT_TRUE(CreateDirectory( FilePath(FilePath::kCurrentDirectory))); - EXPECT_TRUE(file_util::CreateDirectory(top_level)); + EXPECT_TRUE(CreateDirectory(top_level)); #if defined(OS_WIN) FilePath invalid_drive(FILE_PATH_LITERAL("o:\\")); FilePath invalid_path = invalid_drive.Append(FILE_PATH_LITERAL("some\\inaccessible\\dir")); - if (!base::PathExists(invalid_drive)) { - EXPECT_FALSE(file_util::CreateDirectory(invalid_path)); + if (!PathExists(invalid_drive)) { + EXPECT_FALSE(CreateDirectory(invalid_path)); } #endif } @@ -1808,20 +1693,20 @@ TEST_F(FileUtilTest, DetectDirectoryTest) { // Check a directory FilePath test_root = temp_dir_.path().Append(FILE_PATH_LITERAL("detect_directory_test")); - EXPECT_FALSE(base::PathExists(test_root)); - EXPECT_TRUE(file_util::CreateDirectory(test_root)); - EXPECT_TRUE(base::PathExists(test_root)); + EXPECT_FALSE(PathExists(test_root)); + EXPECT_TRUE(CreateDirectory(test_root)); + EXPECT_TRUE(PathExists(test_root)); EXPECT_TRUE(DirectoryExists(test_root)); // Check a file FilePath test_path = test_root.Append(FILE_PATH_LITERAL("foobar.txt")); - EXPECT_FALSE(base::PathExists(test_path)); + EXPECT_FALSE(PathExists(test_path)); CreateTextFile(test_path, L"test file"); - EXPECT_TRUE(base::PathExists(test_path)); + EXPECT_TRUE(PathExists(test_path)); EXPECT_FALSE(DirectoryExists(test_path)); - EXPECT_TRUE(base::DeleteFile(test_path, false)); + EXPECT_TRUE(DeleteFile(test_path, false)); - EXPECT_TRUE(base::DeleteFile(test_root, true)); + EXPECT_TRUE(DeleteFile(test_root, true)); } TEST_F(FileUtilTest, FileEnumeratorTest) { @@ -1839,11 +1724,11 @@ TEST_F(FileUtilTest, FileEnumeratorTest) { // create the directories FilePath dir1 = temp_dir_.path().Append(FPL("dir1")); - EXPECT_TRUE(file_util::CreateDirectory(dir1)); + EXPECT_TRUE(CreateDirectory(dir1)); FilePath dir2 = temp_dir_.path().Append(FPL("dir2")); - EXPECT_TRUE(file_util::CreateDirectory(dir2)); + EXPECT_TRUE(CreateDirectory(dir2)); FilePath dir2inner = dir2.Append(FPL("inner")); - EXPECT_TRUE(file_util::CreateDirectory(dir2inner)); + EXPECT_TRUE(CreateDirectory(dir2inner)); // create the files FilePath dir2file = dir2.Append(FPL("dir2file.txt")); @@ -1929,7 +1814,7 @@ TEST_F(FileUtilTest, FileEnumeratorTest) { ReparsePoint reparse_point(dir1, dir2); EXPECT_TRUE(reparse_point.IsValid()); - if ((base::win::GetVersion() >= base::win::VERSION_VISTA)) { + if ((win::GetVersion() >= win::VERSION_VISTA)) { // There can be a delay for the enumeration code to see the change on // the file system so skip this test for XP. // Enumerate the reparse point. @@ -1977,16 +1862,16 @@ TEST_F(FileUtilTest, AppendToFile) { temp_dir_.path().Append(FILE_PATH_LITERAL("FilePathTest")); // Create a fresh, empty copy of this directory. - if (base::PathExists(data_dir)) { - ASSERT_TRUE(base::DeleteFile(data_dir, true)); + if (PathExists(data_dir)) { + ASSERT_TRUE(DeleteFile(data_dir, true)); } - ASSERT_TRUE(file_util::CreateDirectory(data_dir)); + ASSERT_TRUE(CreateDirectory(data_dir)); // Create a fresh, empty copy of this directory. - if (base::PathExists(data_dir)) { - ASSERT_TRUE(base::DeleteFile(data_dir, true)); + if (PathExists(data_dir)) { + ASSERT_TRUE(DeleteFile(data_dir, true)); } - ASSERT_TRUE(file_util::CreateDirectory(data_dir)); + ASSERT_TRUE(CreateDirectory(data_dir)); FilePath foobar(data_dir.Append(FILE_PATH_LITERAL("foobar.txt"))); std::string data("hello"); @@ -2005,30 +1890,30 @@ TEST_F(FileUtilTest, TouchFile) { temp_dir_.path().Append(FILE_PATH_LITERAL("FilePathTest")); // Create a fresh, empty copy of this directory. - if (base::PathExists(data_dir)) { - ASSERT_TRUE(base::DeleteFile(data_dir, true)); + if (PathExists(data_dir)) { + ASSERT_TRUE(DeleteFile(data_dir, true)); } - ASSERT_TRUE(file_util::CreateDirectory(data_dir)); + ASSERT_TRUE(CreateDirectory(data_dir)); FilePath foobar(data_dir.Append(FILE_PATH_LITERAL("foobar.txt"))); std::string data("hello"); ASSERT_TRUE(file_util::WriteFile(foobar, data.c_str(), data.length())); - base::Time access_time; + Time access_time; // This timestamp is divisible by one day (in local timezone), // to make it work on FAT too. - ASSERT_TRUE(base::Time::FromString("Wed, 16 Nov 1994, 00:00:00", + ASSERT_TRUE(Time::FromString("Wed, 16 Nov 1994, 00:00:00", &access_time)); - base::Time modification_time; + Time modification_time; // Note that this timestamp is divisible by two (seconds) - FAT stores // modification times with 2s resolution. - ASSERT_TRUE(base::Time::FromString("Tue, 15 Nov 1994, 12:45:26 GMT", + ASSERT_TRUE(Time::FromString("Tue, 15 Nov 1994, 12:45:26 GMT", &modification_time)); - ASSERT_TRUE(file_util::TouchFile(foobar, access_time, modification_time)); - base::PlatformFileInfo file_info; - ASSERT_TRUE(file_util::GetFileInfo(foobar, &file_info)); + ASSERT_TRUE(TouchFile(foobar, access_time, modification_time)); + PlatformFileInfo file_info; + ASSERT_TRUE(GetFileInfo(foobar, &file_info)); EXPECT_EQ(file_info.last_accessed.ToInternalValue(), access_time.ToInternalValue()); EXPECT_EQ(file_info.last_modified.ToInternalValue(), @@ -2038,17 +1923,17 @@ TEST_F(FileUtilTest, TouchFile) { TEST_F(FileUtilTest, IsDirectoryEmpty) { FilePath empty_dir = temp_dir_.path().Append(FILE_PATH_LITERAL("EmptyDir")); - ASSERT_FALSE(base::PathExists(empty_dir)); + ASSERT_FALSE(PathExists(empty_dir)); - ASSERT_TRUE(file_util::CreateDirectory(empty_dir)); + ASSERT_TRUE(CreateDirectory(empty_dir)); - EXPECT_TRUE(file_util::IsDirectoryEmpty(empty_dir)); + EXPECT_TRUE(IsDirectoryEmpty(empty_dir)); FilePath foo(empty_dir.Append(FILE_PATH_LITERAL("foo.txt"))); std::string bar("baz"); ASSERT_TRUE(file_util::WriteFile(foo, bar.c_str(), bar.length())); - EXPECT_FALSE(file_util::IsDirectoryEmpty(empty_dir)); + EXPECT_FALSE(IsDirectoryEmpty(empty_dir)); } #if defined(OS_POSIX) @@ -2072,10 +1957,10 @@ class VerifyPathControlledByUserTest : public FileUtilTest { // |-> text_file_ base_dir_ = temp_dir_.path().AppendASCII("base_dir"); - ASSERT_TRUE(file_util::CreateDirectory(base_dir_)); + ASSERT_TRUE(CreateDirectory(base_dir_)); sub_dir_ = base_dir_.AppendASCII("sub_dir"); - ASSERT_TRUE(file_util::CreateDirectory(sub_dir_)); + ASSERT_TRUE(CreateDirectory(sub_dir_)); text_file_ = sub_dir_.AppendASCII("file.txt"); CreateTextFile(text_file_, L"This text file has some text in it."); @@ -2096,11 +1981,9 @@ class VerifyPathControlledByUserTest : public FileUtilTest { // Users and group can read, write, traverse int enabled_permissions = - file_util::FILE_PERMISSION_USER_MASK | - file_util::FILE_PERMISSION_GROUP_MASK; + FILE_PERMISSION_USER_MASK | FILE_PERMISSION_GROUP_MASK; // Other users can't read, write, traverse - int disabled_permissions = - file_util::FILE_PERMISSION_OTHERS_MASK; + int disabled_permissions = FILE_PERMISSION_OTHERS_MASK; ASSERT_NO_FATAL_FAILURE( ChangePosixFilePermissions( @@ -2150,7 +2033,7 @@ TEST_F(VerifyPathControlledByUserTest, Symlinks) { // Symlink to the file at the end of the path. FilePath file_link = base_dir_.AppendASCII("file_link"); - ASSERT_TRUE(file_util::CreateSymbolicLink(text_file_, file_link)) + ASSERT_TRUE(CreateSymbolicLink(text_file_, file_link)) << "Failed to create symlink."; EXPECT_FALSE( @@ -2162,11 +2045,11 @@ TEST_F(VerifyPathControlledByUserTest, Symlinks) { // Symlink from one directory to another within the path. FilePath link_to_sub_dir = base_dir_.AppendASCII("link_to_sub_dir"); - ASSERT_TRUE(file_util::CreateSymbolicLink(sub_dir_, link_to_sub_dir)) + ASSERT_TRUE(CreateSymbolicLink(sub_dir_, link_to_sub_dir)) << "Failed to create symlink."; FilePath file_path_with_link = link_to_sub_dir.AppendASCII("file.txt"); - ASSERT_TRUE(base::PathExists(file_path_with_link)); + ASSERT_TRUE(PathExists(file_path_with_link)); EXPECT_FALSE( file_util::VerifyPathControlledByUser( @@ -2421,6 +2304,53 @@ TEST_F(VerifyPathControlledByUserTest, WriteBitChecks) { sub_dir_, text_file_, uid_, ok_gids_)); } +#if defined(OS_ANDROID) +TEST_F(FileUtilTest, ValidContentUriTest) { + // Get the test image path. + FilePath data_dir; + ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &data_dir)); + data_dir = data_dir.AppendASCII("file_util"); + ASSERT_TRUE(PathExists(data_dir)); + FilePath image_file = data_dir.Append(FILE_PATH_LITERAL("red.png")); + int64 image_size; + GetFileSize(image_file, &image_size); + EXPECT_LT(0, image_size); + + // Insert the image into MediaStore. MediaStore will do some conversions, and + // return the content URI. + FilePath path = file_util::InsertImageIntoMediaStore(image_file); + EXPECT_TRUE(path.IsContentUri()); + EXPECT_TRUE(PathExists(path)); + // The file size may not equal to the input image as MediaStore may convert + // the image. + int64 content_uri_size; + GetFileSize(path, &content_uri_size); + EXPECT_EQ(image_size, content_uri_size); + + // We should be able to read the file. + char* buffer = new char[image_size]; + int fd = OpenContentUriForRead(path); + EXPECT_LT(0, fd); + EXPECT_TRUE(ReadFromFD(fd, buffer, image_size)); + delete[] buffer; +} + +TEST_F(FileUtilTest, NonExistentContentUriTest) { + FilePath path("content://foo.bar"); + EXPECT_TRUE(path.IsContentUri()); + EXPECT_FALSE(PathExists(path)); + // Size should be smaller than 0. + int64 size; + EXPECT_FALSE(GetFileSize(path, &size)); + + // We should not be able to read the file. + int fd = OpenContentUriForRead(path); + EXPECT_EQ(-1, fd); +} +#endif + #endif // defined(OS_POSIX) } // namespace + +} // namespace base diff --git a/chromium/base/file_util_win.cc b/chromium/base/file_util_win.cc index 39317a3a932..44c1205aa65 100644 --- a/chromium/base/file_util_win.cc +++ b/chromium/base/file_util_win.cc @@ -92,7 +92,7 @@ bool DeleteFile(const FilePath& path, bool recursive) { // If not recursing, then first check to see if |path| is a directory. // If it is, then remove it with RemoveDirectory. PlatformFileInfo file_info; - if (file_util::GetFileInfo(path, &file_info) && file_info.is_directory) + if (GetFileInfo(path, &file_info) && file_info.is_directory) return RemoveDirectory(path.value().c_str()) != 0; // Otherwise, it's a file, wildcard or non-existant. Try DeleteFile first @@ -107,8 +107,6 @@ bool DeleteFile(const FilePath& path, bool recursive) { // into the rest of the buffer. wchar_t double_terminated_path[MAX_PATH + 1] = {0}; #pragma warning(suppress:4996) // don't complain about wcscpy deprecation - if (g_bug108724_debug) - LOG(WARNING) << "copying "; wcscpy(double_terminated_path, path.value().c_str()); SHFILEOPSTRUCT file_operation = {0}; @@ -117,11 +115,7 @@ bool DeleteFile(const FilePath& path, bool recursive) { file_operation.fFlags = FOF_NOERRORUI | FOF_SILENT | FOF_NOCONFIRMATION; if (!recursive) file_operation.fFlags |= FOF_NORECURSION | FOF_FILESONLY; - if (g_bug108724_debug) - LOG(WARNING) << "Performing shell operation"; int err = SHFileOperation(&file_operation); - if (g_bug108724_debug) - LOG(WARNING) << "Done: " << err; // Since we're passing flags to the operation telling it to be silent, // it's possible for the operation to be aborted/cancelled without err @@ -184,7 +178,7 @@ bool CopyDirectory(const FilePath& from_path, const FilePath& to_path, // Except that Vista fails to do that, and instead do a recursive copy if // the target directory doesn't exist. if (base::win::GetVersion() >= base::win::VERSION_VISTA) - file_util::CreateDirectory(to_path); + CreateDirectory(to_path); else ShellCopy(from_path, to_path, false); } @@ -219,19 +213,7 @@ bool DirectoryExists(const FilePath& path) { return false; } -} // namespace base - -// ----------------------------------------------------------------------------- - -namespace file_util { - -using base::DirectoryExists; -using base::FilePath; -using base::kFileShareAll; - bool GetTempDir(FilePath* path) { - base::ThreadRestrictions::AssertIOAllowed(); - wchar_t temp_path[MAX_PATH + 1]; DWORD path_len = ::GetTempPath(MAX_PATH, temp_path); if (path_len >= MAX_PATH || path_len <= 0) @@ -243,12 +225,12 @@ bool GetTempDir(FilePath* path) { return true; } -bool GetShmemTempDir(FilePath* path, bool executable) { +bool GetShmemTempDir(bool executable, FilePath* path) { return GetTempDir(path); } bool CreateTemporaryFile(FilePath* path) { - base::ThreadRestrictions::AssertIOAllowed(); + ThreadRestrictions::AssertIOAllowed(); FilePath temp_file; @@ -264,7 +246,7 @@ bool CreateTemporaryFile(FilePath* path) { } FILE* CreateAndOpenTemporaryShmemFile(FilePath* path, bool executable) { - base::ThreadRestrictions::AssertIOAllowed(); + ThreadRestrictions::AssertIOAllowed(); return CreateAndOpenTemporaryFile(path); } @@ -273,7 +255,7 @@ FILE* CreateAndOpenTemporaryShmemFile(FilePath* path, bool executable) { // TODO(jrg): is there equivalent call to use on Windows instead of // going 2-step? FILE* CreateAndOpenTemporaryFileInDir(const FilePath& dir, FilePath* path) { - base::ThreadRestrictions::AssertIOAllowed(); + ThreadRestrictions::AssertIOAllowed(); if (!CreateTemporaryFileInDir(dir, path)) { return NULL; } @@ -283,14 +265,14 @@ FILE* CreateAndOpenTemporaryFileInDir(const FilePath& dir, FilePath* path) { return OpenFile(*path, "wb+"); } -bool CreateTemporaryFileInDir(const FilePath& dir, - FilePath* temp_file) { - base::ThreadRestrictions::AssertIOAllowed(); +bool CreateTemporaryFileInDir(const FilePath& dir, FilePath* temp_file) { + ThreadRestrictions::AssertIOAllowed(); wchar_t temp_name[MAX_PATH + 1]; if (!GetTempFileName(dir.value().c_str(), L"", 0, temp_name)) { - DPLOG(WARNING) << "Failed to get temporary file name in " << dir.value(); + DPLOG(WARNING) << "Failed to get temporary file name in " + << UTF16ToUTF8(dir.value()); return false; } @@ -311,7 +293,7 @@ bool CreateTemporaryFileInDir(const FilePath& dir, bool CreateTemporaryDirInDir(const FilePath& base_dir, const FilePath::StringType& prefix, FilePath* new_dir) { - base::ThreadRestrictions::AssertIOAllowed(); + ThreadRestrictions::AssertIOAllowed(); FilePath path_to_create; @@ -320,9 +302,9 @@ bool CreateTemporaryDirInDir(const FilePath& base_dir, // the one exists, keep trying another path name until we reach some limit. string16 new_dir_name; new_dir_name.assign(prefix); - new_dir_name.append(base::IntToString16(::base::GetCurrentProcId())); + new_dir_name.append(IntToString16(GetCurrentProcId())); new_dir_name.push_back('_'); - new_dir_name.append(base::IntToString16(base::RandInt(0, kint16max))); + new_dir_name.append(IntToString16(RandInt(0, kint16max))); path_to_create = base_dir.Append(new_dir_name); if (::CreateDirectory(path_to_create.value().c_str(), NULL)) { @@ -336,7 +318,7 @@ bool CreateTemporaryDirInDir(const FilePath& base_dir, bool CreateNewTempDirectory(const FilePath::StringType& prefix, FilePath* new_temp_path) { - base::ThreadRestrictions::AssertIOAllowed(); + ThreadRestrictions::AssertIOAllowed(); FilePath system_temp_dir; if (!GetTempDir(&system_temp_dir)) @@ -346,8 +328,8 @@ bool CreateNewTempDirectory(const FilePath::StringType& prefix, } bool CreateDirectoryAndGetError(const FilePath& full_path, - base::PlatformFileError* error) { - base::ThreadRestrictions::AssertIOAllowed(); + PlatformFileError* error) { + ThreadRestrictions::AssertIOAllowed(); // If the path exists, we've succeeded if it's a directory, failed otherwise. const wchar_t* full_path_str = full_path.value().c_str(); @@ -361,7 +343,7 @@ bool CreateDirectoryAndGetError(const FilePath& full_path, DLOG(WARNING) << "CreateDirectory(" << full_path_str << "), " << "conflicts with existing file."; if (error) { - *error = base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY; + *error = PLATFORM_FILE_ERROR_NOT_A_DIRECTORY; } return false; } @@ -374,14 +356,14 @@ bool CreateDirectoryAndGetError(const FilePath& full_path, FilePath parent_path(full_path.DirName()); if (parent_path.value() == full_path.value()) { if (error) { - *error = base::PLATFORM_FILE_ERROR_NOT_FOUND; + *error = PLATFORM_FILE_ERROR_NOT_FOUND; } return false; } if (!CreateDirectoryAndGetError(parent_path, error)) { DLOG(WARNING) << "Failed to create one of the parent directories."; if (error) { - DCHECK(*error != base::PLATFORM_FILE_OK); + DCHECK(*error != PLATFORM_FILE_OK); } return false; } @@ -396,7 +378,7 @@ bool CreateDirectoryAndGetError(const FilePath& full_path, return true; } else { if (error) - *error = base::LastErrorToPlatformFileError(error_code); + *error = LastErrorToPlatformFileError(error_code); DLOG(WARNING) << "Failed to create directory " << full_path_str << ", last error is " << error_code << "."; return false; @@ -406,14 +388,125 @@ bool CreateDirectoryAndGetError(const FilePath& full_path, } } +bool NormalizeFilePath(const FilePath& path, FilePath* real_path) { + ThreadRestrictions::AssertIOAllowed(); + FilePath mapped_file; + if (!NormalizeToNativeFilePath(path, &mapped_file)) + return false; + // NormalizeToNativeFilePath() will return a path that starts with + // "\Device\Harddisk...". Helper DevicePathToDriveLetterPath() + // will find a drive letter which maps to the path's device, so + // that we return a path starting with a drive letter. + return DevicePathToDriveLetterPath(mapped_file, real_path); +} + +bool DevicePathToDriveLetterPath(const FilePath& nt_device_path, + FilePath* out_drive_letter_path) { + ThreadRestrictions::AssertIOAllowed(); + + // Get the mapping of drive letters to device paths. + const int kDriveMappingSize = 1024; + wchar_t drive_mapping[kDriveMappingSize] = {'\0'}; + if (!::GetLogicalDriveStrings(kDriveMappingSize - 1, drive_mapping)) { + DLOG(ERROR) << "Failed to get drive mapping."; + return false; + } + + // The drive mapping is a sequence of null terminated strings. + // The last string is empty. + wchar_t* drive_map_ptr = drive_mapping; + wchar_t device_path_as_string[MAX_PATH]; + wchar_t drive[] = L" :"; + + // For each string in the drive mapping, get the junction that links + // to it. If that junction is a prefix of |device_path|, then we + // know that |drive| is the real path prefix. + while (*drive_map_ptr) { + drive[0] = drive_map_ptr[0]; // Copy the drive letter. + + if (QueryDosDevice(drive, device_path_as_string, MAX_PATH)) { + FilePath device_path(device_path_as_string); + if (device_path == nt_device_path || + device_path.IsParent(nt_device_path)) { + *out_drive_letter_path = FilePath(drive + + nt_device_path.value().substr(wcslen(device_path_as_string))); + return true; + } + } + // Move to the next drive letter string, which starts one + // increment after the '\0' that terminates the current string. + while (*drive_map_ptr++); + } + + // No drive matched. The path does not start with a device junction + // that is mounted as a drive letter. This means there is no drive + // letter path to the volume that holds |device_path|, so fail. + return false; +} + +bool NormalizeToNativeFilePath(const FilePath& path, FilePath* nt_path) { + ThreadRestrictions::AssertIOAllowed(); + // In Vista, GetFinalPathNameByHandle() would give us the real path + // from a file handle. If we ever deprecate XP, consider changing the + // code below to a call to GetFinalPathNameByHandle(). The method this + // function uses is explained in the following msdn article: + // http://msdn.microsoft.com/en-us/library/aa366789(VS.85).aspx + base::win::ScopedHandle file_handle( + ::CreateFile(path.value().c_str(), + GENERIC_READ, + kFileShareAll, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL)); + if (!file_handle) + return false; + + // Create a file mapping object. Can't easily use MemoryMappedFile, because + // we only map the first byte, and need direct access to the handle. You can + // not map an empty file, this call fails in that case. + base::win::ScopedHandle file_map_handle( + ::CreateFileMapping(file_handle.Get(), + NULL, + PAGE_READONLY, + 0, + 1, // Just one byte. No need to look at the data. + NULL)); + if (!file_map_handle) + return false; + + // Use a view of the file to get the path to the file. + void* file_view = MapViewOfFile(file_map_handle.Get(), + FILE_MAP_READ, 0, 0, 1); + if (!file_view) + return false; + + // The expansion of |path| into a full path may make it longer. + // GetMappedFileName() will fail if the result is longer than MAX_PATH. + // Pad a bit to be safe. If kMaxPathLength is ever changed to be less + // than MAX_PATH, it would be nessisary to test that GetMappedFileName() + // not return kMaxPathLength. This would mean that only part of the + // path fit in |mapped_file_path|. + const int kMaxPathLength = MAX_PATH + 10; + wchar_t mapped_file_path[kMaxPathLength]; + bool success = false; + HANDLE cp = GetCurrentProcess(); + if (::GetMappedFileNameW(cp, file_view, mapped_file_path, kMaxPathLength)) { + *nt_path = FilePath(mapped_file_path); + success = true; + } + ::UnmapViewOfFile(file_view); + return success; +} + // TODO(rkc): Work out if we want to handle NTFS junctions here or not, handle // them if we do decide to. bool IsLink(const FilePath& file_path) { return false; } -bool GetFileInfo(const FilePath& file_path, base::PlatformFileInfo* results) { - base::ThreadRestrictions::AssertIOAllowed(); +bool GetFileInfo(const FilePath& file_path, PlatformFileInfo* results) { + ThreadRestrictions::AssertIOAllowed(); WIN32_FILE_ATTRIBUTE_DATA attr; if (!GetFileAttributesEx(file_path.value().c_str(), @@ -428,26 +521,21 @@ bool GetFileInfo(const FilePath& file_path, base::PlatformFileInfo* results) { results->is_directory = (attr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; - results->last_modified = base::Time::FromFileTime(attr.ftLastWriteTime); - results->last_accessed = base::Time::FromFileTime(attr.ftLastAccessTime); - results->creation_time = base::Time::FromFileTime(attr.ftCreationTime); + results->last_modified = Time::FromFileTime(attr.ftLastWriteTime); + results->last_accessed = Time::FromFileTime(attr.ftLastAccessTime); + results->creation_time = Time::FromFileTime(attr.ftCreationTime); return true; } FILE* OpenFile(const FilePath& filename, const char* mode) { - base::ThreadRestrictions::AssertIOAllowed(); + ThreadRestrictions::AssertIOAllowed(); std::wstring w_mode = ASCIIToWide(std::string(mode)); return _wfsopen(filename.value().c_str(), w_mode.c_str(), _SH_DENYNO); } -FILE* OpenFile(const std::string& filename, const char* mode) { - base::ThreadRestrictions::AssertIOAllowed(); - return _fsopen(filename.c_str(), mode, _SH_DENYNO); -} - int ReadFile(const FilePath& filename, char* data, int size) { - base::ThreadRestrictions::AssertIOAllowed(); + ThreadRestrictions::AssertIOAllowed(); base::win::ScopedHandle file(CreateFile(filename.value().c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, @@ -465,6 +553,21 @@ int ReadFile(const FilePath& filename, char* data, int size) { return -1; } +} // namespace base + +// ----------------------------------------------------------------------------- + +namespace file_util { + +using base::DirectoryExists; +using base::FilePath; +using base::kFileShareAll; + +FILE* OpenFile(const std::string& filename, const char* mode) { + base::ThreadRestrictions::AssertIOAllowed(); + return _fsopen(filename.c_str(), mode, _SH_DENYNO); +} + int WriteFile(const FilePath& filename, const char* data, int size) { base::ThreadRestrictions::AssertIOAllowed(); base::win::ScopedHandle file(CreateFile(filename.value().c_str(), @@ -553,117 +656,6 @@ bool SetCurrentDirectory(const FilePath& directory) { return ret != 0; } -bool NormalizeFilePath(const FilePath& path, FilePath* real_path) { - base::ThreadRestrictions::AssertIOAllowed(); - FilePath mapped_file; - if (!NormalizeToNativeFilePath(path, &mapped_file)) - return false; - // NormalizeToNativeFilePath() will return a path that starts with - // "\Device\Harddisk...". Helper DevicePathToDriveLetterPath() - // will find a drive letter which maps to the path's device, so - // that we return a path starting with a drive letter. - return DevicePathToDriveLetterPath(mapped_file, real_path); -} - -bool DevicePathToDriveLetterPath(const FilePath& nt_device_path, - FilePath* out_drive_letter_path) { - base::ThreadRestrictions::AssertIOAllowed(); - - // Get the mapping of drive letters to device paths. - const int kDriveMappingSize = 1024; - wchar_t drive_mapping[kDriveMappingSize] = {'\0'}; - if (!::GetLogicalDriveStrings(kDriveMappingSize - 1, drive_mapping)) { - DLOG(ERROR) << "Failed to get drive mapping."; - return false; - } - - // The drive mapping is a sequence of null terminated strings. - // The last string is empty. - wchar_t* drive_map_ptr = drive_mapping; - wchar_t device_path_as_string[MAX_PATH]; - wchar_t drive[] = L" :"; - - // For each string in the drive mapping, get the junction that links - // to it. If that junction is a prefix of |device_path|, then we - // know that |drive| is the real path prefix. - while (*drive_map_ptr) { - drive[0] = drive_map_ptr[0]; // Copy the drive letter. - - if (QueryDosDevice(drive, device_path_as_string, MAX_PATH)) { - FilePath device_path(device_path_as_string); - if (device_path == nt_device_path || - device_path.IsParent(nt_device_path)) { - *out_drive_letter_path = FilePath(drive + - nt_device_path.value().substr(wcslen(device_path_as_string))); - return true; - } - } - // Move to the next drive letter string, which starts one - // increment after the '\0' that terminates the current string. - while (*drive_map_ptr++); - } - - // No drive matched. The path does not start with a device junction - // that is mounted as a drive letter. This means there is no drive - // letter path to the volume that holds |device_path|, so fail. - return false; -} - -bool NormalizeToNativeFilePath(const FilePath& path, FilePath* nt_path) { - base::ThreadRestrictions::AssertIOAllowed(); - // In Vista, GetFinalPathNameByHandle() would give us the real path - // from a file handle. If we ever deprecate XP, consider changing the - // code below to a call to GetFinalPathNameByHandle(). The method this - // function uses is explained in the following msdn article: - // http://msdn.microsoft.com/en-us/library/aa366789(VS.85).aspx - base::win::ScopedHandle file_handle( - ::CreateFile(path.value().c_str(), - GENERIC_READ, - kFileShareAll, - NULL, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, - NULL)); - if (!file_handle) - return false; - - // Create a file mapping object. Can't easily use MemoryMappedFile, because - // we only map the first byte, and need direct access to the handle. You can - // not map an empty file, this call fails in that case. - base::win::ScopedHandle file_map_handle( - ::CreateFileMapping(file_handle.Get(), - NULL, - PAGE_READONLY, - 0, - 1, // Just one byte. No need to look at the data. - NULL)); - if (!file_map_handle) - return false; - - // Use a view of the file to get the path to the file. - void* file_view = MapViewOfFile(file_map_handle.Get(), - FILE_MAP_READ, 0, 0, 1); - if (!file_view) - return false; - - // The expansion of |path| into a full path may make it longer. - // GetMappedFileName() will fail if the result is longer than MAX_PATH. - // Pad a bit to be safe. If kMaxPathLength is ever changed to be less - // than MAX_PATH, it would be nessisary to test that GetMappedFileName() - // not return kMaxPathLength. This would mean that only part of the - // path fit in |mapped_file_path|. - const int kMaxPathLength = MAX_PATH + 10; - wchar_t mapped_file_path[kMaxPathLength]; - bool success = false; - HANDLE cp = GetCurrentProcess(); - if (::GetMappedFileNameW(cp, file_view, mapped_file_path, kMaxPathLength)) { - *nt_path = FilePath(mapped_file_path); - success = true; - } - ::UnmapViewOfFile(file_view); - return success; -} - int GetMaximumPathComponentLength(const FilePath& path) { base::ThreadRestrictions::AssertIOAllowed(); diff --git a/chromium/base/file_version_info.h b/chromium/base/file_version_info.h index 59cd45df07d..e18ba1367f0 100644 --- a/chromium/base/file_version_info.h +++ b/chromium/base/file_version_info.h @@ -66,21 +66,21 @@ class FileVersionInfo { // Accessors to the different version properties. // Returns an empty string if the property is not found. - virtual string16 company_name() = 0; - virtual string16 company_short_name() = 0; - virtual string16 product_name() = 0; - virtual string16 product_short_name() = 0; - virtual string16 internal_name() = 0; - virtual string16 product_version() = 0; - virtual string16 private_build() = 0; - virtual string16 special_build() = 0; - virtual string16 comments() = 0; - virtual string16 original_filename() = 0; - virtual string16 file_description() = 0; - virtual string16 file_version() = 0; - virtual string16 legal_copyright() = 0; - virtual string16 legal_trademarks() = 0; - virtual string16 last_change() = 0; + virtual base::string16 company_name() = 0; + virtual base::string16 company_short_name() = 0; + virtual base::string16 product_name() = 0; + virtual base::string16 product_short_name() = 0; + virtual base::string16 internal_name() = 0; + virtual base::string16 product_version() = 0; + virtual base::string16 private_build() = 0; + virtual base::string16 special_build() = 0; + virtual base::string16 comments() = 0; + virtual base::string16 original_filename() = 0; + virtual base::string16 file_description() = 0; + virtual base::string16 file_version() = 0; + virtual base::string16 legal_copyright() = 0; + virtual base::string16 legal_trademarks() = 0; + virtual base::string16 last_change() = 0; virtual bool is_official_build() = 0; }; diff --git a/chromium/base/files/dir_reader_linux.h b/chromium/base/files/dir_reader_linux.h index 3e0721ec26c..cb0cbd38e56 100644 --- a/chromium/base/files/dir_reader_linux.h +++ b/chromium/base/files/dir_reader_linux.h @@ -37,7 +37,7 @@ class DirReaderLinux { ~DirReaderLinux() { if (fd_ >= 0) { - if (HANDLE_EINTR(close(fd_))) + if (IGNORE_EINTR(close(fd_))) RAW_LOG(ERROR, "Failed to close directory handle"); } } diff --git a/chromium/base/files/file.cc b/chromium/base/files/file.cc new file mode 100644 index 00000000000..4902f15a2ff --- /dev/null +++ b/chromium/base/files/file.cc @@ -0,0 +1,64 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/files/file.h" + +// TODO(rvargas): remove this (needed for kInvalidPlatformFileValue). +#include "base/platform_file.h" + +namespace base { + +File::Info::Info() + : size(0), + is_directory(false), + is_symbolic_link(false) { +} + +File::Info::~Info() { +} + +File::File() + : file_(kInvalidPlatformFileValue), + error_(FILE_OK), + created_(false), + async_(false) { +} + +#if !defined(OS_NACL) +File::File(const FilePath& name, uint32 flags) + : file_(kInvalidPlatformFileValue), + error_(FILE_OK), + created_(false), + async_(false) { + if (name.ReferencesParent()) { + error_ = FILE_ERROR_ACCESS_DENIED; + return; + } + CreateBaseFileUnsafe(name, flags); +} +#endif + +File::File(RValue other) + : file_(other.object->TakePlatformFile()), + error_(other.object->error()), + created_(other.object->created()), + async_(other.object->async_) { +} + +File::~File() { + Close(); +} + +File& File::operator=(RValue other) { + if (this != other.object) { + Close(); + SetPlatformFile(other.object->TakePlatformFile()); + error_ = other.object->error(); + created_ = other.object->created(); + async_ = other.object->async_; + } + return *this; +} + +} // namespace base diff --git a/chromium/base/files/file.h b/chromium/base/files/file.h new file mode 100644 index 00000000000..d1e0e8ca587 --- /dev/null +++ b/chromium/base/files/file.h @@ -0,0 +1,280 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_FILES_FILE_H_ +#define BASE_FILES_FILE_H_ + +#include "build/build_config.h" +#if defined(OS_WIN) +#include <windows.h> +#endif + +#include <string> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "base/move.h" +#include "base/time/time.h" + +#if defined(OS_WIN) +#include "base/win/scoped_handle.h" +#endif + +namespace base { + +#if defined(OS_WIN) +typedef HANDLE PlatformFile; +#elif defined(OS_POSIX) +typedef int PlatformFile; +#endif + + +// Thin wrapper around an OS-level file. +// Note that this class does not provide any support for asynchronous IO, other +// than the ability to create asynchronous handles on Windows. +// +// Note about const: this class does not attempt to determine if the underlying +// file system object is affected by a particular method in order to consider +// that method const or not. Only methods that deal with member variables in an +// obvious non-modifying way are marked as const. Any method that forward calls +// to the OS is not considered const, even if there is no apparent change to +// member variables. +class BASE_EXPORT File { + MOVE_ONLY_TYPE_FOR_CPP_03(File, RValue) + + public: + // FLAG_(OPEN|CREATE).* are mutually exclusive. You should specify exactly one + // of the five (possibly combining with other flags) when opening or creating + // a file. + // FLAG_(WRITE|APPEND) are mutually exclusive. This is so that APPEND behavior + // will be consistent with O_APPEND on POSIX. + // FLAG_EXCLUSIVE_(READ|WRITE) only grant exclusive access to the file on + // creation on POSIX; for existing files, consider using Lock(). + enum Flags { + FLAG_OPEN = 1 << 0, // Opens a file, only if it exists. + FLAG_CREATE = 1 << 1, // Creates a new file, only if it does not + // already exist. + FLAG_OPEN_ALWAYS = 1 << 2, // May create a new file. + FLAG_CREATE_ALWAYS = 1 << 3, // May overwrite an old file. + FLAG_OPEN_TRUNCATED = 1 << 4, // Opens a file and truncates it, only if it + // exists. + FLAG_READ = 1 << 5, + FLAG_WRITE = 1 << 6, + FLAG_APPEND = 1 << 7, + FLAG_EXCLUSIVE_READ = 1 << 8, // EXCLUSIVE is opposite of Windows SHARE. + FLAG_EXCLUSIVE_WRITE = 1 << 9, + FLAG_ASYNC = 1 << 10, + FLAG_TEMPORARY = 1 << 11, // Used on Windows only. + FLAG_HIDDEN = 1 << 12, // Used on Windows only. + FLAG_DELETE_ON_CLOSE = 1 << 13, + FLAG_WRITE_ATTRIBUTES = 1 << 14, // Used on Windows only. + FLAG_SHARE_DELETE = 1 << 15, // Used on Windows only. + FLAG_TERMINAL_DEVICE = 1 << 16, // Serial port flags. + FLAG_BACKUP_SEMANTICS = 1 << 17, // Used on Windows only. + FLAG_EXECUTE = 1 << 18, // Used on Windows only. + }; + + // This enum has been recorded in multiple histograms. If the order of the + // fields needs to change, please ensure that those histograms are obsolete or + // have been moved to a different enum. + // + // FILE_ERROR_ACCESS_DENIED is returned when a call fails because of a + // filesystem restriction. FILE_ERROR_SECURITY is returned when a browser + // policy doesn't allow the operation to be executed. + enum Error { + FILE_OK = 0, + FILE_ERROR_FAILED = -1, + FILE_ERROR_IN_USE = -2, + FILE_ERROR_EXISTS = -3, + FILE_ERROR_NOT_FOUND = -4, + FILE_ERROR_ACCESS_DENIED = -5, + FILE_ERROR_TOO_MANY_OPENED = -6, + FILE_ERROR_NO_MEMORY = -7, + FILE_ERROR_NO_SPACE = -8, + FILE_ERROR_NOT_A_DIRECTORY = -9, + FILE_ERROR_INVALID_OPERATION = -10, + FILE_ERROR_SECURITY = -11, + FILE_ERROR_ABORT = -12, + FILE_ERROR_NOT_A_FILE = -13, + FILE_ERROR_NOT_EMPTY = -14, + FILE_ERROR_INVALID_URL = -15, + FILE_ERROR_IO = -16, + // Put new entries here and increment FILE_ERROR_MAX. + FILE_ERROR_MAX = -17 + }; + + // This explicit mapping matches both FILE_ on Windows and SEEK_ on Linux. + enum Whence { + FROM_BEGIN = 0, + FROM_CURRENT = 1, + FROM_END = 2 + }; + + // Used to hold information about a given file. + // If you add more fields to this structure (platform-specific fields are OK), + // make sure to update all functions that use it in file_util_{win|posix}.cc + // too, and the ParamTraits<base::PlatformFileInfo> implementation in + // chrome/common/common_param_traits.cc. + struct BASE_EXPORT Info { + Info(); + ~Info(); + + // The size of the file in bytes. Undefined when is_directory is true. + int64 size; + + // True if the file corresponds to a directory. + bool is_directory; + + // True if the file corresponds to a symbolic link. + bool is_symbolic_link; + + // The last modified time of a file. + base::Time last_modified; + + // The last accessed time of a file. + base::Time last_accessed; + + // The creation time of a file. + base::Time creation_time; + }; + + File(); + + // Creates or opens the given file. This will fail with 'access denied' if the + // |name| contains path traversal ('..') components. + File(const FilePath& name, uint32 flags); + + // Takes ownership of |platform_file|. + explicit File(PlatformFile platform_file); + + // Move constructor for C++03 move emulation of this type. + File(RValue other); + + ~File(); + + // Move operator= for C++03 move emulation of this type. + File& operator=(RValue other); + + // Creates or opens the given file, allowing paths with traversal ('..') + // components. Use only with extreme care. + void CreateBaseFileUnsafe(const FilePath& name, uint32 flags); + + bool IsValid() const; + + // Returns true if a new file was created (or an old one truncated to zero + // length to simulate a new file, which can happen with + // FLAG_CREATE_ALWAYS), and false otherwise. + bool created() const { return created_; } + + // Returns the OS result of opening this file. + Error error() const { return error_; } + + PlatformFile GetPlatformFile() const { return file_; } + PlatformFile TakePlatformFile(); + + // Destroying this object closes the file automatically. + void Close(); + + // Changes current position in the file to an |offset| relative to an origin + // defined by |whence|. Returns the resultant current position in the file + // (relative to the start) or -1 in case of error. + int64 Seek(Whence whence, int64 offset); + + // Reads the given number of bytes (or until EOF is reached) starting with the + // given offset. Returns the number of bytes read, or -1 on error. Note that + // this function makes a best effort to read all data on all platforms, so it + // is not intended for stream oriented files but instead for cases when the + // normal expectation is that actually |size| bytes are read unless there is + // an error. + int Read(int64 offset, char* data, int size); + + // Same as above but without seek. + int ReadAtCurrentPos(char* data, int size); + + // Reads the given number of bytes (or until EOF is reached) starting with the + // given offset, but does not make any effort to read all data on all + // platforms. Returns the number of bytes read, or -1 on error. + int ReadNoBestEffort(int64 offset, char* data, int size); + + // Same as above but without seek. + int ReadAtCurrentPosNoBestEffort(char* data, int size); + + // Writes the given buffer into the file at the given offset, overwritting any + // data that was previously there. Returns the number of bytes written, or -1 + // on error. Note that this function makes a best effort to write all data on + // all platforms. + // Ignores the offset and writes to the end of the file if the file was opened + // with FLAG_APPEND. + int Write(int64 offset, const char* data, int size); + + // Save as above but without seek. + int WriteAtCurrentPos(const char* data, int size); + + // Save as above but does not make any effort to write all data on all + // platforms. Returns the number of bytes written, or -1 on error. + int WriteAtCurrentPosNoBestEffort(const char* data, int size); + + // Truncates the file to the given length. If |length| is greater than the + // current size of the file, the file is extended with zeros. If the file + // doesn't exist, |false| is returned. + bool Truncate(int64 length); + + // Flushes the buffers. + bool Flush(); + + // Updates the file times. + bool SetTimes(Time last_access_time, Time last_modified_time); + + // Returns some basic information for the given file. + bool GetInfo(Info* info); + + // Attempts to take an exclusive write lock on the file. Returns immediately + // (i.e. does not wait for another process to unlock the file). If the lock + // was obtained, the result will be FILE_OK. A lock only guarantees + // that other processes may not also take a lock on the same file with the + // same API - it may still be opened, renamed, unlinked, etc. + // + // Common semantics: + // * Locks are held by processes, but not inherited by child processes. + // * Locks are released by the OS on file close or process termination. + // * Locks are reliable only on local filesystems. + // * Duplicated file handles may also write to locked files. + // Windows-specific semantics: + // * Locks are mandatory for read/write APIs, advisory for mapping APIs. + // * Within a process, locking the same file (by the same or new handle) + // will fail. + // POSIX-specific semantics: + // * Locks are advisory only. + // * Within a process, locking the same file (by the same or new handle) + // will succeed. + // * Closing any descriptor on a given file releases the lock. + Error Lock(); + + // Unlock a file previously locked. + Error Unlock(); + +#if defined(OS_WIN) + static Error OSErrorToFileError(DWORD last_error); +#elif defined(OS_POSIX) + static Error OSErrorToFileError(int saved_errno); +#endif + + private: + void SetPlatformFile(PlatformFile file); + +#if defined(OS_WIN) + win::ScopedHandle file_; +#elif defined(OS_POSIX) + PlatformFile file_; +#endif + + Error error_; + bool created_; + bool async_; +}; + +} // namespace base + +#endif // BASE_FILES_FILE_H_ diff --git a/chromium/base/files/file_enumerator.h b/chromium/base/files/file_enumerator.h index ce9bd1fd265..38bb8337c70 100644 --- a/chromium/base/files/file_enumerator.h +++ b/chromium/base/files/file_enumerator.h @@ -53,6 +53,9 @@ class BASE_EXPORT FileEnumerator { Time GetLastModifiedTime() const; #if defined(OS_WIN) + // Note that the cAlternateFileName (used to hold the "short" 8.3 name) + // of the WIN32_FIND_DATA will be empty. Since we don't use short file + // names, we tell Windows to omit it which speeds up the query slightly. const WIN32_FIND_DATA& find_data() const { return find_data_; } #elif defined(OS_POSIX) const struct stat& stat() const { return stat_; } diff --git a/chromium/base/files/file_enumerator_win.cc b/chromium/base/files/file_enumerator_win.cc index e47f5421a71..6da1667ed99 100644 --- a/chromium/base/files/file_enumerator_win.cc +++ b/chromium/base/files/file_enumerator_win.cc @@ -8,6 +8,7 @@ #include "base/logging.h" #include "base/threading/thread_restrictions.h" +#include "base/win/windows_version.h" namespace base { @@ -99,7 +100,18 @@ FilePath FileEnumerator::Next() { else src = src.Append(pattern_); - find_handle_ = FindFirstFile(src.value().c_str(), &find_data_); + if (base::win::GetVersion() >= base::win::VERSION_WIN7) { + // Use a "large fetch" on newer Windows which should speed up large + // enumerations (we seldom abort in the middle). + find_handle_ = FindFirstFileEx(src.value().c_str(), + FindExInfoBasic, // Omit short name. + &find_data_, + FindExSearchNameMatch, + NULL, + FIND_FIRST_EX_LARGE_FETCH); + } else { + find_handle_ = FindFirstFile(src.value().c_str(), &find_data_); + } has_find_data_ = true; } else { // Search for the next file/directory. diff --git a/chromium/base/files/file_path.cc b/chromium/base/files/file_path.cc index be346342937..3ea5856a0d5 100644 --- a/chromium/base/files/file_path.cc +++ b/chromium/base/files/file_path.cc @@ -107,18 +107,21 @@ bool AreAllSeparators(const StringType& input) { // Find the position of the '.' that separates the extension from the rest // of the file name. The position is relative to BaseName(), not value(). -// This allows a second extension component of up to 4 characters when the -// rightmost extension component is a common double extension (gz, bz2, Z). -// For example, foo.tar.gz or foo.tar.Z would have extension components of -// '.tar.gz' and '.tar.Z' respectively. Returns npos if it can't find an -// extension. -StringType::size_type ExtensionSeparatorPosition(const StringType& path) { +// Returns npos if it can't find an extension. +StringType::size_type FinalExtensionSeparatorPosition(const StringType& path) { // Special case "." and ".." if (path == FilePath::kCurrentDirectory || path == FilePath::kParentDirectory) return StringType::npos; - const StringType::size_type last_dot = - path.rfind(FilePath::kExtensionSeparator); + return path.rfind(FilePath::kExtensionSeparator); +} + +// Same as above, but allow a second extension component of up to 4 +// characters when the rightmost extension component is a common double +// extension (gz, bz2, Z). For example, foo.tar.gz or foo.tar.Z would have +// extension components of '.tar.gz' and '.tar.Z' respectively. +StringType::size_type ExtensionSeparatorPosition(const StringType& path) { + const StringType::size_type last_dot = FinalExtensionSeparatorPosition(path); // No extension, or the extension is the whole filename. if (last_dot == StringType::npos || last_dot == 0U) @@ -370,6 +373,15 @@ StringType FilePath::Extension() const { return base.path_.substr(dot, StringType::npos); } +StringType FilePath::FinalExtension() const { + FilePath base(BaseName()); + const StringType::size_type dot = FinalExtensionSeparatorPosition(base.path_); + if (dot == StringType::npos) + return StringType(); + + return base.path_.substr(dot, StringType::npos); +} + FilePath FilePath::RemoveExtension() const { if (Extension().empty()) return *this; @@ -381,6 +393,17 @@ FilePath FilePath::RemoveExtension() const { return FilePath(path_.substr(0, dot)); } +FilePath FilePath::RemoveFinalExtension() const { + if (FinalExtension().empty()) + return *this; + + const StringType::size_type dot = FinalExtensionSeparatorPosition(path_); + if (dot == StringType::npos) + return *this; + + return FilePath(path_.substr(0, dot)); +} + FilePath FilePath::InsertBeforeExtension(const StringType& suffix) const { if (suffix.empty()) return FilePath(path_); @@ -585,14 +608,6 @@ string16 FilePath::AsUTF16Unsafe() const { #endif } -// The *Hack functions are temporary while we fix the remainder of the code. -// Remember to remove the #includes at the top when you remove these. - -// static -FilePath FilePath::FromWStringHack(const std::wstring& wstring) { - return FilePath(SysWideToNativeMB(wstring)); -} - // static FilePath FilePath::FromUTF8Unsafe(const std::string& utf8) { #if defined(OS_MACOSX) || defined(OS_CHROMEOS) @@ -631,11 +646,6 @@ string16 FilePath::AsUTF16Unsafe() const { } // static -FilePath FilePath::FromWStringHack(const std::wstring& wstring) { - return FilePath(wstring); -} - -// static FilePath FilePath::FromUTF8Unsafe(const std::string& utf8) { return FilePath(UTF8ToWide(utf8)); } @@ -1293,6 +1303,12 @@ FilePath FilePath::NormalizePathSeparators() const { #endif } +#if defined(OS_ANDROID) +bool FilePath::IsContentUri() const { + return StartsWithASCII(path_, "content://", false /*case_sensitive*/); +} +#endif + } // namespace base void PrintTo(const base::FilePath& path, std::ostream* out) { diff --git a/chromium/base/files/file_path.h b/chromium/base/files/file_path.h index 8b69ea493a1..f4b8ff8ade8 100644 --- a/chromium/base/files/file_path.h +++ b/chromium/base/files/file_path.h @@ -224,18 +224,33 @@ class BASE_EXPORT FilePath { // Returns ".jpg" for path "C:\pics\jojo.jpg", or an empty string if // the file has no extension. If non-empty, Extension() will always start // with precisely one ".". The following code should always work regardless - // of the value of path. + // of the value of path. For common double-extensions like .tar.gz and + // .user.js, this method returns the combined extension. For a single + // component, use FinalExtension(). // new_path = path.RemoveExtension().value().append(path.Extension()); // ASSERT(new_path == path.value()); // NOTE: this is different from the original file_util implementation which // returned the extension without a leading "." ("jpg" instead of ".jpg") StringType Extension() const; + // Returns the path's file extension, as in Extension(), but will + // never return a double extension. + // + // TODO(davidben): Check all our extension-sensitive code to see if + // we can rename this to Extension() and the other to something like + // LongExtension(), defaulting to short extensions and leaving the + // long "extensions" to logic like file_util::GetUniquePathNumber(). + StringType FinalExtension() const; + // Returns "C:\pics\jojo" for path "C:\pics\jojo.jpg" // NOTE: this is slightly different from the similar file_util implementation // which returned simply 'jojo'. FilePath RemoveExtension() const WARN_UNUSED_RESULT; + // Removes the path's file extension, as in RemoveExtension(), but + // ignores double extensions. + FilePath RemoveFinalExtension() const WARN_UNUSED_RESULT; + // Inserts |suffix| after the file name portion of |path| but before the // extension. Returns "" if BaseName() == "." or "..". // Examples: @@ -332,24 +347,6 @@ class BASE_EXPORT FilePath { // Similar to AsUTF8Unsafe, but returns UTF-16 instead. string16 AsUTF16Unsafe() const; - // Older Chromium code assumes that paths are always wstrings. - // This function converts wstrings to FilePaths, and is - // useful to smooth porting that old code to the FilePath API. - // It has "Hack" its name so people feel bad about using it. - // http://code.google.com/p/chromium/issues/detail?id=24672 - // - // If you are trying to be a good citizen and remove these, ask yourself: - // - Am I interacting with other Chrome code that deals with files? Then - // try to convert the API into using FilePath. - // - Am I interacting with OS-native calls? Then use value() to get at an - // OS-native string format. - // - Am I using well-known file names, like "config.ini"? Then use the - // ASCII functions (we require paths to always be supersets of ASCII). - // - Am I displaying a string to the user in some UI? Then use the - // LossyDisplayName() function, but keep in mind that you can't - // ever use the result of that again as a path. - static FilePath FromWStringHack(const std::wstring& wstring); - // Returns a FilePath object from a path name in UTF-8. This function // should only be used for cases where you are sure that the input // string is UTF-8. @@ -405,6 +402,15 @@ class BASE_EXPORT FilePath { const StringType& string2); #endif +#if defined(OS_ANDROID) + // On android, file selection dialog can return a file with content uri + // scheme(starting with content://). Content uri needs to be opened with + // ContentResolver to guarantee that the app has appropriate permissions + // to access it. + // Returns true if the path is a content uri, or false otherwise. + bool IsContentUri() const; +#endif + private: // Remove trailing separators from this object. If the path is absolute, it // will never be stripped any more than to refer to the absolute root diff --git a/chromium/base/files/file_path_unittest.cc b/chromium/base/files/file_path_unittest.cc index 8b2fcf531c9..fa7627c2f9b 100644 --- a/chromium/base/files/file_path_unittest.cc +++ b/chromium/base/files/file_path_unittest.cc @@ -708,6 +708,7 @@ TEST_F(FilePathTest, Extension) { FilePath jpg = base_dir.Append(FILE_PATH_LITERAL("foo.jpg")); EXPECT_EQ(FILE_PATH_LITERAL(".jpg"), jpg.Extension()); + EXPECT_EQ(FILE_PATH_LITERAL(".jpg"), jpg.FinalExtension()); FilePath base = jpg.BaseName().RemoveExtension(); EXPECT_EQ(FILE_PATH_LITERAL("foo"), base.value()); @@ -717,6 +718,7 @@ TEST_F(FilePathTest, Extension) { EXPECT_EQ(path_no_ext.value(), path_no_ext.RemoveExtension().value()); EXPECT_EQ(FILE_PATH_LITERAL(""), path_no_ext.Extension()); + EXPECT_EQ(FILE_PATH_LITERAL(""), path_no_ext.FinalExtension()); } TEST_F(FilePathTest, Extension2) { @@ -739,16 +741,9 @@ TEST_F(FilePathTest, Extension2) { { FPL("/foo/bar/"), FPL("") }, { FPL("/foo/bar./"), FPL(".") }, { FPL("/foo/bar/baz.ext1.ext2"), FPL(".ext2") }, - { FPL("/foo.tar.gz"), FPL(".tar.gz") }, - { FPL("/foo.tar.Z"), FPL(".tar.Z") }, - { FPL("/foo.tar.bz2"), FPL(".tar.bz2") }, { FPL("/subversion-1.6.12.zip"), FPL(".zip") }, - { FPL("/foo.1234.gz"), FPL(".1234.gz") }, { FPL("/foo.12345.gz"), FPL(".gz") }, { FPL("/foo..gz"), FPL(".gz") }, - { FPL("/foo.1234.tar.gz"), FPL(".tar.gz") }, - { FPL("/foo.tar.tar.gz"), FPL(".tar.gz") }, - { FPL("/foo.tar.gz.gz"), FPL(".gz.gz") }, { FPL("."), FPL("") }, { FPL(".."), FPL("") }, { FPL("./foo"), FPL("") }, @@ -757,14 +752,32 @@ TEST_F(FilePathTest, Extension2) { { FPL("/foo.bar////"), FPL(".bar") }, { FPL("/foo.bar/.."), FPL("") }, { FPL("/foo.bar/..////"), FPL("") }, - { FPL("/foo.1234.user.js"), FPL(".user.js") }, - { FPL("foo.user.js"), FPL(".user.js") }, { FPL("/foo.1234.luser.js"), FPL(".js") }, { FPL("/user.js"), FPL(".js") }, }; + const struct UnaryTestData double_extension_cases[] = { + { FPL("/foo.tar.gz"), FPL(".tar.gz") }, + { FPL("/foo.tar.Z"), FPL(".tar.Z") }, + { FPL("/foo.tar.bz2"), FPL(".tar.bz2") }, + { FPL("/foo.1234.gz"), FPL(".1234.gz") }, + { FPL("/foo.1234.tar.gz"), FPL(".tar.gz") }, + { FPL("/foo.tar.tar.gz"), FPL(".tar.gz") }, + { FPL("/foo.tar.gz.gz"), FPL(".gz.gz") }, + { FPL("/foo.1234.user.js"), FPL(".user.js") }, + { FPL("foo.user.js"), FPL(".user.js") }, + }; for (unsigned int i = 0; i < arraysize(cases); ++i) { FilePath path(cases[i].input); FilePath::StringType extension = path.Extension(); + FilePath::StringType final_extension = path.FinalExtension(); + EXPECT_STREQ(cases[i].expected, extension.c_str()) << "i: " << i << + ", path: " << path.value(); + EXPECT_STREQ(cases[i].expected, final_extension.c_str()) << "i: " << i << + ", path: " << path.value(); + } + for (unsigned int i = 0; i < arraysize(double_extension_cases); ++i) { + FilePath path(cases[i].input); + FilePath::StringType extension = path.Extension(); EXPECT_STREQ(cases[i].expected, extension.c_str()) << "i: " << i << ", path: " << path.value(); } @@ -850,7 +863,6 @@ TEST_F(FilePathTest, RemoveExtension) { { FPL("foo."), FPL("foo") }, { FPL("foo.."), FPL("foo.") }, { FPL("foo.baz.dll"), FPL("foo.baz") }, - { FPL("foo.tar.gz"), FPL("foo") }, #if defined(FILE_PATH_USES_WIN_SEPARATORS) { FPL("C:\\foo.bar\\foo"), FPL("C:\\foo.bar\\foo") }, { FPL("C:\\foo.bar\\..\\\\"), FPL("C:\\foo.bar\\..\\\\") }, @@ -861,8 +873,19 @@ TEST_F(FilePathTest, RemoveExtension) { for (unsigned int i = 0; i < arraysize(cases); ++i) { FilePath path(cases[i].input); FilePath removed = path.RemoveExtension(); + FilePath removed_final = path.RemoveFinalExtension(); EXPECT_EQ(cases[i].expected, removed.value()) << "i: " << i << ", path: " << path.value(); + EXPECT_EQ(cases[i].expected, removed_final.value()) << "i: " << i << + ", path: " << path.value(); + } + { + FilePath path(FPL("foo.tar.gz")); + FilePath removed = path.RemoveExtension(); + FilePath removed_final = path.RemoveFinalExtension(); + EXPECT_EQ(FPL("foo"), removed.value()) << ", path: " << path.value(); + EXPECT_EQ(FPL("foo.tar"), removed_final.value()) << ", path: " + << path.value(); } } @@ -1228,4 +1251,33 @@ TEST_F(FilePathTest, AsEndingWithSeparator) { } } +#if defined(OS_ANDROID) +TEST_F(FilePathTest, ContentUriTest) { + const struct UnaryBooleanTestData cases[] = { + { FPL("content://foo.bar"), true }, + { FPL("content://foo.bar/"), true }, + { FPL("content://foo/bar"), true }, + { FPL("CoNTenT://foo.bar"), true }, + { FPL("content://"), true }, + { FPL("content:///foo.bar"), true }, + { FPL("content://3foo/bar"), true }, + { FPL("content://_foo/bar"), true }, + { FPL(".. "), false }, + { FPL("foo.bar"), false }, + { FPL("content:foo.bar"), false }, + { FPL("content:/foo.ba"), false }, + { FPL("content:/dir/foo.bar"), false }, + { FPL("content: //foo.bar"), false }, + { FPL("content%2a%2f%2f"), false }, + }; + + for (size_t i = 0; i < arraysize(cases); ++i) { + FilePath input(cases[i].input); + bool observed = input.IsContentUri(); + EXPECT_EQ(cases[i].expected, observed) << + "i: " << i << ", input: " << input.value(); + } +} +#endif + } // namespace base diff --git a/chromium/base/files/file_path_watcher_browsertest.cc b/chromium/base/files/file_path_watcher_browsertest.cc index 69ff80608ae..aed409c7871 100644 --- a/chromium/base/files/file_path_watcher_browsertest.cc +++ b/chromium/base/files/file_path_watcher_browsertest.cc @@ -138,19 +138,6 @@ void SetupWatchCallback(const FilePath& target, completion->Signal(); } -void QuitLoopWatchCallback(MessageLoop* loop, - const FilePath& expected_path, - bool expected_error, - bool* flag, - const FilePath& path, - bool error) { - ASSERT_TRUE(flag); - *flag = true; - EXPECT_EQ(expected_path, path); - EXPECT_EQ(expected_error, error); - loop->PostTask(FROM_HERE, loop->QuitWhenIdleClosure()); -} - class FilePathWatcherTest : public testing::Test { public: FilePathWatcherTest() @@ -352,7 +339,7 @@ TEST_F(FilePathWatcherTest, NonExistentDirectory) { scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); ASSERT_TRUE(SetupWatch(file, &watcher, delegate.get(), false)); - ASSERT_TRUE(file_util::CreateDirectory(dir)); + ASSERT_TRUE(base::CreateDirectory(dir)); ASSERT_TRUE(WriteFile(file, "content")); @@ -389,7 +376,7 @@ TEST_F(FilePathWatcherTest, DirectoryChain) { for (std::vector<std::string>::const_iterator d(dir_names.begin()); d != dir_names.end(); ++d) { sub_path = sub_path.AppendASCII(*d); - ASSERT_TRUE(file_util::CreateDirectory(sub_path)); + ASSERT_TRUE(base::CreateDirectory(sub_path)); } VLOG(1) << "Create File"; ASSERT_TRUE(WriteFile(file, "content")); @@ -410,7 +397,7 @@ TEST_F(FilePathWatcherTest, DisappearingDirectory) { FilePathWatcher watcher; FilePath dir(temp_dir_.path().AppendASCII("dir")); FilePath file(dir.AppendASCII("file")); - ASSERT_TRUE(file_util::CreateDirectory(dir)); + ASSERT_TRUE(base::CreateDirectory(dir)); ASSERT_TRUE(WriteFile(file, "content")); scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); ASSERT_TRUE(SetupWatch(file, &watcher, delegate.get(), false)); @@ -445,7 +432,7 @@ TEST_F(FilePathWatcherTest, WatchDirectory) { scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); ASSERT_TRUE(SetupWatch(dir, &watcher, delegate.get(), false)); - ASSERT_TRUE(file_util::CreateDirectory(dir)); + ASSERT_TRUE(base::CreateDirectory(dir)); VLOG(1) << "Waiting for directory creation"; ASSERT_TRUE(WaitForEvents()); @@ -484,7 +471,7 @@ TEST_F(FilePathWatcherTest, MoveParent) { false)); // Setup a directory hierarchy. - ASSERT_TRUE(file_util::CreateDirectory(subdir)); + ASSERT_TRUE(base::CreateDirectory(subdir)); ASSERT_TRUE(WriteFile(file, "content")); VLOG(1) << "Waiting for file creation"; ASSERT_TRUE(WaitForEvents()); @@ -505,7 +492,7 @@ TEST_F(FilePathWatcherTest, RecursiveWatch) { ASSERT_TRUE(SetupWatch(dir, &watcher, delegate.get(), true)); // Main directory("dir") creation. - ASSERT_TRUE(file_util::CreateDirectory(dir)); + ASSERT_TRUE(base::CreateDirectory(dir)); ASSERT_TRUE(WaitForEvents()); // Create "$dir/file1". @@ -515,7 +502,7 @@ TEST_F(FilePathWatcherTest, RecursiveWatch) { // Create "$dir/subdir". FilePath subdir(dir.AppendASCII("subdir")); - ASSERT_TRUE(file_util::CreateDirectory(subdir)); + ASSERT_TRUE(base::CreateDirectory(subdir)); ASSERT_TRUE(WaitForEvents()); // Create "$dir/subdir/subdir_file1". @@ -525,7 +512,7 @@ TEST_F(FilePathWatcherTest, RecursiveWatch) { // Create "$dir/subdir/subdir_child_dir". FilePath subdir_child_dir(subdir.AppendASCII("subdir_child_dir")); - ASSERT_TRUE(file_util::CreateDirectory(subdir_child_dir)); + ASSERT_TRUE(base::CreateDirectory(subdir_child_dir)); ASSERT_TRUE(WaitForEvents()); // Create "$dir/subdir/subdir_child_dir/child_dir_file1". @@ -572,7 +559,7 @@ TEST_F(FilePathWatcherTest, MoveChild) { FilePath dest_file(dest_subdir.AppendASCII("file")); // Setup a directory hierarchy. - ASSERT_TRUE(file_util::CreateDirectory(source_subdir)); + ASSERT_TRUE(base::CreateDirectory(source_subdir)); ASSERT_TRUE(WriteFile(source_file, "content")); scoped_ptr<TestDelegate> file_delegate(new TestDelegate(collector())); @@ -618,7 +605,7 @@ TEST_F(FilePathWatcherTest, CreateLink) { // Now make sure we get notified if the link is created. // Note that test_file() doesn't have to exist. - ASSERT_TRUE(file_util::CreateSymbolicLink(test_file(), test_link())); + ASSERT_TRUE(CreateSymbolicLink(test_file(), test_link())); ASSERT_TRUE(WaitForEvents()); DeleteDelegateOnFileThread(delegate.release()); } @@ -628,7 +615,7 @@ TEST_F(FilePathWatcherTest, DeleteLink) { // Unfortunately this test case only works if the link target exists. // TODO(craig) fix this as part of crbug.com/91561. ASSERT_TRUE(WriteFile(test_file(), "content")); - ASSERT_TRUE(file_util::CreateSymbolicLink(test_file(), test_link())); + ASSERT_TRUE(CreateSymbolicLink(test_file(), test_link())); FilePathWatcher watcher; scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); ASSERT_TRUE(SetupWatch(test_link(), &watcher, delegate.get(), false)); @@ -643,7 +630,7 @@ TEST_F(FilePathWatcherTest, DeleteLink) { // when we are watching the link is caught. TEST_F(FilePathWatcherTest, ModifiedLinkedFile) { ASSERT_TRUE(WriteFile(test_file(), "content")); - ASSERT_TRUE(file_util::CreateSymbolicLink(test_file(), test_link())); + ASSERT_TRUE(CreateSymbolicLink(test_file(), test_link())); FilePathWatcher watcher; scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); // Note that we are watching the symlink. @@ -658,7 +645,7 @@ TEST_F(FilePathWatcherTest, ModifiedLinkedFile) { // Verify that creating a target file that a link is pointing to // when we are watching the link is caught. TEST_F(FilePathWatcherTest, CreateTargetLinkedFile) { - ASSERT_TRUE(file_util::CreateSymbolicLink(test_file(), test_link())); + ASSERT_TRUE(CreateSymbolicLink(test_file(), test_link())); FilePathWatcher watcher; scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); // Note that we are watching the symlink. @@ -674,7 +661,7 @@ TEST_F(FilePathWatcherTest, CreateTargetLinkedFile) { // when we are watching the link is caught. TEST_F(FilePathWatcherTest, DeleteTargetLinkedFile) { ASSERT_TRUE(WriteFile(test_file(), "content")); - ASSERT_TRUE(file_util::CreateSymbolicLink(test_file(), test_link())); + ASSERT_TRUE(CreateSymbolicLink(test_file(), test_link())); FilePathWatcher watcher; scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); // Note that we are watching the symlink. @@ -696,12 +683,12 @@ TEST_F(FilePathWatcherTest, LinkedDirectoryPart1) { FilePath linkfile(link_dir.AppendASCII("file")); scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); // dir/file should exist. - ASSERT_TRUE(file_util::CreateDirectory(dir)); + ASSERT_TRUE(base::CreateDirectory(dir)); ASSERT_TRUE(WriteFile(file, "content")); // Note that we are watching dir.lnk/file which doesn't exist yet. ASSERT_TRUE(SetupWatch(linkfile, &watcher, delegate.get(), false)); - ASSERT_TRUE(file_util::CreateSymbolicLink(dir, link_dir)); + ASSERT_TRUE(CreateSymbolicLink(dir, link_dir)); VLOG(1) << "Waiting for link creation"; ASSERT_TRUE(WaitForEvents()); @@ -726,11 +713,11 @@ TEST_F(FilePathWatcherTest, LinkedDirectoryPart2) { scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); // Now create the link from dir.lnk pointing to dir but // neither dir nor dir/file exist yet. - ASSERT_TRUE(file_util::CreateSymbolicLink(dir, link_dir)); + ASSERT_TRUE(CreateSymbolicLink(dir, link_dir)); // Note that we are watching dir.lnk/file. ASSERT_TRUE(SetupWatch(linkfile, &watcher, delegate.get(), false)); - ASSERT_TRUE(file_util::CreateDirectory(dir)); + ASSERT_TRUE(base::CreateDirectory(dir)); ASSERT_TRUE(WriteFile(file, "content")); VLOG(1) << "Waiting for dir/file creation"; ASSERT_TRUE(WaitForEvents()); @@ -754,8 +741,8 @@ TEST_F(FilePathWatcherTest, LinkedDirectoryPart3) { FilePath file(dir.AppendASCII("file")); FilePath linkfile(link_dir.AppendASCII("file")); scoped_ptr<TestDelegate> delegate(new TestDelegate(collector())); - ASSERT_TRUE(file_util::CreateDirectory(dir)); - ASSERT_TRUE(file_util::CreateSymbolicLink(dir, link_dir)); + ASSERT_TRUE(base::CreateDirectory(dir)); + ASSERT_TRUE(CreateSymbolicLink(dir, link_dir)); // Note that we are watching dir.lnk/file but the file doesn't exist yet. ASSERT_TRUE(SetupWatch(linkfile, &watcher, delegate.get(), false)); @@ -781,8 +768,8 @@ enum Permission { Execute }; +#if defined(OS_MACOSX) bool ChangeFilePermissions(const FilePath& path, Permission perm, bool allow) { -#if defined(OS_POSIX) struct stat stat_buf; if (stat(path.value().c_str(), &stat_buf) != 0) @@ -809,61 +796,8 @@ bool ChangeFilePermissions(const FilePath& path, Permission perm, bool allow) { stat_buf.st_mode &= ~mode; } return chmod(path.value().c_str(), stat_buf.st_mode) == 0; - -#elif defined(OS_WIN) - PACL old_dacl; - PSECURITY_DESCRIPTOR security_descriptor; - if (GetNamedSecurityInfo(const_cast<wchar_t*>(path.value().c_str()), - SE_FILE_OBJECT, - DACL_SECURITY_INFORMATION, NULL, NULL, &old_dacl, - NULL, &security_descriptor) != ERROR_SUCCESS) - return false; - - DWORD mode = 0; - switch (perm) { - case Read: - mode = GENERIC_READ; - break; - case Write: - mode = GENERIC_WRITE; - break; - case Execute: - mode = GENERIC_EXECUTE; - break; - default: - ADD_FAILURE() << "unknown perm " << perm; - return false; - } - - // Deny Read access for the current user. - EXPLICIT_ACCESS change; - change.grfAccessPermissions = mode; - change.grfAccessMode = allow ? GRANT_ACCESS : DENY_ACCESS; - change.grfInheritance = 0; - change.Trustee.pMultipleTrustee = NULL; - change.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; - change.Trustee.TrusteeForm = TRUSTEE_IS_NAME; - change.Trustee.TrusteeType = TRUSTEE_IS_USER; - change.Trustee.ptstrName = L"CURRENT_USER"; - - PACL new_dacl; - if (SetEntriesInAcl(1, &change, old_dacl, &new_dacl) != ERROR_SUCCESS) { - LocalFree(security_descriptor); - return false; - } - - DWORD rc = SetNamedSecurityInfo(const_cast<wchar_t*>(path.value().c_str()), - SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, - NULL, NULL, new_dacl, NULL); - LocalFree(security_descriptor); - LocalFree(new_dacl); - - return rc == ERROR_SUCCESS; -#else - NOTIMPLEMENTED(); - return false; -#endif } +#endif // defined(OS_MACOSX) #if defined(OS_MACOSX) // Linux implementation of FilePathWatcher doesn't catch attribute changes. @@ -878,8 +812,8 @@ TEST_F(FilePathWatcherTest, DirAttributesChanged) { FilePath test_dir2(test_dir1.AppendASCII("DirAttributesChangedDir2")); FilePath test_file(test_dir2.AppendASCII("DirAttributesChangedFile")); // Setup a directory hierarchy. - ASSERT_TRUE(file_util::CreateDirectory(test_dir1)); - ASSERT_TRUE(file_util::CreateDirectory(test_dir2)); + ASSERT_TRUE(base::CreateDirectory(test_dir1)); + ASSERT_TRUE(base::CreateDirectory(test_dir2)); ASSERT_TRUE(WriteFile(test_file, "content")); FilePathWatcher watcher; diff --git a/chromium/base/files/file_path_watcher_kqueue.cc b/chromium/base/files/file_path_watcher_kqueue.cc index 2ffb83622c2..e035f22caa5 100644 --- a/chromium/base/files/file_path_watcher_kqueue.cc +++ b/chromium/base/files/file_path_watcher_kqueue.cc @@ -210,7 +210,7 @@ void FilePathWatcherImpl::CloseFileDescriptor(uintptr_t* fd) { return; } - if (HANDLE_EINTR(close(*fd)) != 0) { + if (IGNORE_EINTR(close(*fd)) != 0) { DPLOG(ERROR) << "close"; } *fd = kNoFileDescriptor; @@ -497,7 +497,7 @@ void FilePathWatcherImpl::CancelOnMessageLoopThread() { if (!is_cancelled()) { set_cancelled(); kqueue_watcher_.StopWatchingFileDescriptor(); - if (HANDLE_EINTR(close(kqueue_)) != 0) { + if (IGNORE_EINTR(close(kqueue_)) != 0) { DPLOG(ERROR) << "close kqueue"; } kqueue_ = -1; diff --git a/chromium/base/files/file_path_watcher_linux.cc b/chromium/base/files/file_path_watcher_linux.cc index 86a4226bf33..d5052e2e512 100644 --- a/chromium/base/files/file_path_watcher_linux.cc +++ b/chromium/base/files/file_path_watcher_linux.cc @@ -18,6 +18,7 @@ #include "base/bind.h" #include "base/containers/hash_tables.h" +#include "base/debug/trace_event.h" #include "base/file_util.h" #include "base/files/file_path.h" #include "base/lazy_instance.h" @@ -55,7 +56,7 @@ class InotifyReader { void OnInotifyEvent(const inotify_event* event); private: - friend struct ::base::DefaultLazyInstanceTraits<InotifyReader>; + friend struct DefaultLazyInstanceTraits<InotifyReader>; typedef std::set<FilePathWatcherImpl*> WatcherSet; @@ -63,13 +64,13 @@ class InotifyReader { ~InotifyReader(); // We keep track of which delegates want to be notified on which watches. - base::hash_map<Watch, WatcherSet> watchers_; + hash_map<Watch, WatcherSet> watchers_; // Lock to protect watchers_. - base::Lock lock_; + Lock lock_; // Separate thread on which we run blocking read for inotify events. - base::Thread thread_; + Thread thread_; // File descriptor returned by inotify_init. const int inotify_fd_; @@ -158,6 +159,8 @@ void InotifyReaderCallback(InotifyReader* reader, int inotify_fd, CHECK_LE(0, shutdown_fd); CHECK_GT(FD_SETSIZE, shutdown_fd); + debug::TraceLog::GetInstance()->SetCurrentThreadBlocksMessageLoop(); + while (true) { fd_set rfds; FD_ZERO(&rfds); @@ -207,18 +210,21 @@ void InotifyReaderCallback(InotifyReader* reader, int inotify_fd, } } -static base::LazyInstance<InotifyReader>::Leaky g_inotify_reader = +static LazyInstance<InotifyReader>::Leaky g_inotify_reader = LAZY_INSTANCE_INITIALIZER; InotifyReader::InotifyReader() : thread_("inotify_reader"), inotify_fd_(inotify_init()), valid_(false) { + if (inotify_fd_ < 0) + PLOG(ERROR) << "inotify_init() failed"; + shutdown_pipe_[0] = -1; shutdown_pipe_[1] = -1; if (inotify_fd_ >= 0 && pipe(shutdown_pipe_) == 0 && thread_.Start()) { thread_.message_loop()->PostTask( - FROM_HERE, base::Bind(&InotifyReaderCallback, this, inotify_fd_, + FROM_HERE, Bind(&InotifyReaderCallback, this, inotify_fd_, shutdown_pipe_[0])); valid_ = true; } @@ -246,7 +252,7 @@ InotifyReader::Watch InotifyReader::AddWatch( if (!valid_) return kInvalidWatch; - base::AutoLock auto_lock(lock_); + AutoLock auto_lock(lock_); Watch watch = inotify_add_watch(inotify_fd_, path.value().c_str(), IN_CREATE | IN_DELETE | @@ -266,7 +272,7 @@ bool InotifyReader::RemoveWatch(Watch watch, if (!valid_) return false; - base::AutoLock auto_lock(lock_); + AutoLock auto_lock(lock_); watchers_[watch].erase(watcher); @@ -283,7 +289,7 @@ void InotifyReader::OnInotifyEvent(const inotify_event* event) { return; FilePath::StringType child(event->len ? event->name : FILE_PATH_LITERAL("")); - base::AutoLock auto_lock(lock_); + AutoLock auto_lock(lock_); for (WatcherSet::iterator watcher = watchers_[event->wd].begin(); watcher != watchers_[event->wd].end(); @@ -303,7 +309,7 @@ void FilePathWatcherImpl::OnFilePathChanged(InotifyReader::Watch fired_watch, if (!message_loop()->BelongsToCurrentThread()) { // Switch to message_loop_ to access watches_ safely. message_loop()->PostTask(FROM_HERE, - base::Bind(&FilePathWatcherImpl::OnFilePathChanged, + Bind(&FilePathWatcherImpl::OnFilePathChanged, this, fired_watch, child, @@ -370,7 +376,7 @@ bool FilePathWatcherImpl::Watch(const FilePath& path, return false; } - set_message_loop(base::MessageLoopProxy::current().get()); + set_message_loop(MessageLoopProxy::current().get()); callback_ = callback; target_ = path; MessageLoop::current()->AddDestructionObserver(this); @@ -397,7 +403,7 @@ void FilePathWatcherImpl::Cancel() { // Switch to the message_loop_ if necessary so we can access |watches_|. if (!message_loop()->BelongsToCurrentThread()) { message_loop()->PostTask(FROM_HERE, - base::Bind(&FilePathWatcher::CancelWatch, + Bind(&FilePathWatcher::CancelWatch, make_scoped_refptr(this))); } else { CancelOnMessageLoopThread(); @@ -440,9 +446,9 @@ bool FilePathWatcherImpl::UpdateWatches() { if (path_valid) { watch_entry->watch_ = g_inotify_reader.Get().AddWatch(path, this); if ((watch_entry->watch_ == InotifyReader::kInvalidWatch) && - file_util::IsLink(path)) { + base::IsLink(path)) { FilePath link; - if (file_util::ReadSymbolicLink(path, &link)) { + if (ReadSymbolicLink(path, &link)) { if (!link.IsAbsolute()) link = path.DirName().Append(link); // Try watching symlink target directory. If the link target is "/", diff --git a/chromium/base/files/file_path_watcher_win.cc b/chromium/base/files/file_path_watcher_win.cc index ac092a931b9..2abbceacd29 100644 --- a/chromium/base/files/file_path_watcher_win.cc +++ b/chromium/base/files/file_path_watcher_win.cc @@ -76,11 +76,11 @@ class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate, // Keep track of the last modified time of the file. We use nulltime // to represent the file not existing. - base::Time last_modified_; + Time last_modified_; // The time at which we processed the first notification with the // |last_modified_| time stamp. - base::Time first_notification_; + Time first_notification_; DISALLOW_COPY_AND_ASSIGN(FilePathWatcherImpl); }; @@ -90,7 +90,7 @@ bool FilePathWatcherImpl::Watch(const FilePath& path, const FilePathWatcher::Callback& callback) { DCHECK(target_.value().empty()); // Can only watch one path. - set_message_loop(base::MessageLoopProxy::current()); + set_message_loop(MessageLoopProxy::current()); callback_ = callback; target_ = path; recursive_watch_ = recursive; @@ -114,8 +114,8 @@ void FilePathWatcherImpl::Cancel() { // Switch to the file thread if necessary so we can stop |watcher_|. if (!message_loop()->BelongsToCurrentThread()) { message_loop()->PostTask(FROM_HERE, - base::Bind(&FilePathWatcher::CancelWatch, - make_scoped_refptr(this))); + Bind(&FilePathWatcher::CancelWatch, + make_scoped_refptr(this))); } else { CancelOnMessageLoopThread(); } @@ -148,12 +148,12 @@ void FilePathWatcherImpl::OnObjectSignaled(HANDLE object) { } // Check whether the event applies to |target_| and notify the callback. - base::PlatformFileInfo file_info; - bool file_exists = file_util::GetFileInfo(target_, &file_info); + PlatformFileInfo file_info; + bool file_exists = GetFileInfo(target_, &file_info); if (file_exists && (last_modified_.is_null() || last_modified_ != file_info.last_modified)) { last_modified_ = file_info.last_modified; - first_notification_ = base::Time::Now(); + first_notification_ = Time::Now(); callback_.Run(target_, false); } else if (file_exists && !first_notification_.is_null()) { // The target's last modification time is equal to what's on record. This @@ -170,14 +170,13 @@ void FilePathWatcherImpl::OnObjectSignaled(HANDLE object) { // clock has advanced one second from the initial notification. After that // interval, client code is guaranteed to having seen the current revision // of the file. - if (base::Time::Now() - first_notification_ > - base::TimeDelta::FromSeconds(1)) { + if (Time::Now() - first_notification_ > TimeDelta::FromSeconds(1)) { // Stop further notifications for this |last_modification_| time stamp. - first_notification_ = base::Time(); + first_notification_ = Time(); } callback_.Run(target_, false); } else if (!file_exists && !last_modified_.is_null()) { - last_modified_ = base::Time(); + last_modified_ = Time(); callback_.Run(target_, false); } @@ -230,10 +229,10 @@ bool FilePathWatcherImpl::UpdateWatch() { if (handle_ != INVALID_HANDLE_VALUE) DestroyWatch(); - base::PlatformFileInfo file_info; - if (file_util::GetFileInfo(target_, &file_info)) { + PlatformFileInfo file_info; + if (GetFileInfo(target_, &file_info)) { last_modified_ = file_info.last_modified; - first_notification_ = base::Time::Now(); + first_notification_ = Time::Now(); } // Start at the target and walk up the directory chain until we succesfully diff --git a/chromium/base/files/file_posix.cc b/chromium/base/files/file_posix.cc new file mode 100644 index 00000000000..9d97c336aa6 --- /dev/null +++ b/chromium/base/files/file_posix.cc @@ -0,0 +1,470 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/files/file.h" + +#include <errno.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/metrics/sparse_histogram.h" +// TODO(rvargas): remove this (needed for kInvalidPlatformFileValue). +#include "base/platform_file.h" +#include "base/posix/eintr_wrapper.h" +#include "base/strings/utf_string_conversions.h" +#include "base/threading/thread_restrictions.h" + +#if defined(OS_ANDROID) +#include "base/os_compat_android.h" +#endif + +namespace base { + +// Make sure our Whence mappings match the system headers. +COMPILE_ASSERT(File::FROM_BEGIN == SEEK_SET && + File::FROM_CURRENT == SEEK_CUR && + File::FROM_END == SEEK_END, whence_matches_system); + +namespace { + +#if defined(OS_BSD) || defined(OS_MACOSX) || defined(OS_NACL) +typedef struct stat stat_wrapper_t; +static int CallFstat(int fd, stat_wrapper_t *sb) { + base::ThreadRestrictions::AssertIOAllowed(); + return fstat(fd, sb); +} +#else +typedef struct stat64 stat_wrapper_t; +static int CallFstat(int fd, stat_wrapper_t *sb) { + base::ThreadRestrictions::AssertIOAllowed(); + return fstat64(fd, sb); +} +#endif + +// NaCl doesn't provide the following system calls, so either simulate them or +// wrap them in order to minimize the number of #ifdef's in this file. +#if !defined(OS_NACL) +static bool IsOpenAppend(PlatformFile file) { + return (fcntl(file, F_GETFL) & O_APPEND) != 0; +} + +static int CallFtruncate(PlatformFile file, int64 length) { + return HANDLE_EINTR(ftruncate(file, length)); +} + +static int CallFsync(PlatformFile file) { + return HANDLE_EINTR(fsync(file)); +} + +static int CallFutimes(PlatformFile file, const struct timeval times[2]) { +#ifdef __USE_XOPEN2K8 + // futimens should be available, but futimes might not be + // http://pubs.opengroup.org/onlinepubs/9699919799/ + + timespec ts_times[2]; + ts_times[0].tv_sec = times[0].tv_sec; + ts_times[0].tv_nsec = times[0].tv_usec * 1000; + ts_times[1].tv_sec = times[1].tv_sec; + ts_times[1].tv_nsec = times[1].tv_usec * 1000; + + return futimens(file, ts_times); +#else + return futimes(file, times); +#endif +} + +static File::Error CallFctnlFlock(PlatformFile file, bool do_lock) { + struct flock lock; + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_SET; + lock.l_start = 0; + lock.l_len = 0; // Lock entire file. + if (HANDLE_EINTR(fcntl(file, do_lock ? F_SETLK : F_UNLCK, &lock)) == -1) + return File::OSErrorToFileError(errno); + return File::FILE_OK; +} +#else // defined(OS_NACL) + +static bool IsOpenAppend(PlatformFile file) { + // NaCl doesn't implement fcntl. Since NaCl's write conforms to the POSIX + // standard and always appends if the file is opened with O_APPEND, just + // return false here. + return false; +} + +static int CallFtruncate(PlatformFile file, int64 length) { + NOTIMPLEMENTED(); // NaCl doesn't implement ftruncate. + return 0; +} + +static int CallFsync(PlatformFile file) { + NOTIMPLEMENTED(); // NaCl doesn't implement fsync. + return 0; +} + +static int CallFutimes(PlatformFile file, const struct timeval times[2]) { + NOTIMPLEMENTED(); // NaCl doesn't implement futimes. + return 0; +} + +static File::Error CallFctnlFlock(PlatformFile file, bool do_lock) { + NOTIMPLEMENTED(); // NaCl doesn't implement flock struct. + return File::FILE_ERROR_INVALID_OPERATION; +} +#endif // defined(OS_NACL) + +} // namespace + +// NaCl doesn't implement system calls to open files directly. +#if !defined(OS_NACL) +// TODO(erikkay): does it make sense to support FLAG_EXCLUSIVE_* here? +void File::CreateBaseFileUnsafe(const FilePath& name, uint32 flags) { + base::ThreadRestrictions::AssertIOAllowed(); + DCHECK(!IsValid()); + DCHECK(!(flags & FLAG_ASYNC)); + + int open_flags = 0; + if (flags & FLAG_CREATE) + open_flags = O_CREAT | O_EXCL; + + created_ = false; + + if (flags & FLAG_CREATE_ALWAYS) { + DCHECK(!open_flags); + open_flags = O_CREAT | O_TRUNC; + } + + if (flags & FLAG_OPEN_TRUNCATED) { + DCHECK(!open_flags); + DCHECK(flags & FLAG_WRITE); + open_flags = O_TRUNC; + } + + if (!open_flags && !(flags & FLAG_OPEN) && !(flags & FLAG_OPEN_ALWAYS)) { + NOTREACHED(); + errno = EOPNOTSUPP; + error_ = FILE_ERROR_FAILED; + return; + } + + if (flags & FLAG_WRITE && flags & FLAG_READ) { + open_flags |= O_RDWR; + } else if (flags & FLAG_WRITE) { + open_flags |= O_WRONLY; + } else if (!(flags & FLAG_READ) && + !(flags & FLAG_WRITE_ATTRIBUTES) && + !(flags & FLAG_APPEND) && + !(flags & FLAG_OPEN_ALWAYS)) { + NOTREACHED(); + } + + if (flags & FLAG_TERMINAL_DEVICE) + open_flags |= O_NOCTTY | O_NDELAY; + + if (flags & FLAG_APPEND && flags & FLAG_READ) + open_flags |= O_APPEND | O_RDWR; + else if (flags & FLAG_APPEND) + open_flags |= O_APPEND | O_WRONLY; + + COMPILE_ASSERT(O_RDONLY == 0, O_RDONLY_must_equal_zero); + + int mode = S_IRUSR | S_IWUSR; +#if defined(OS_CHROMEOS) + mode |= S_IRGRP | S_IROTH; +#endif + + int descriptor = HANDLE_EINTR(open(name.value().c_str(), open_flags, mode)); + + if (flags & FLAG_OPEN_ALWAYS) { + if (descriptor < 0) { + open_flags |= O_CREAT; + if (flags & FLAG_EXCLUSIVE_READ || flags & FLAG_EXCLUSIVE_WRITE) + open_flags |= O_EXCL; // together with O_CREAT implies O_NOFOLLOW + + descriptor = HANDLE_EINTR(open(name.value().c_str(), open_flags, mode)); + if (descriptor >= 0) + created_ = true; + } + } + + if (descriptor >= 0 && (flags & (FLAG_CREATE_ALWAYS | FLAG_CREATE))) + created_ = true; + + if ((descriptor >= 0) && (flags & FLAG_DELETE_ON_CLOSE)) + unlink(name.value().c_str()); + + if (descriptor >= 0) + error_ = FILE_OK; + else + error_ = File::OSErrorToFileError(errno); + + file_ = descriptor; +} +#endif // !defined(OS_NACL) + +bool File::IsValid() const { + return file_ >= 0; +} + +PlatformFile File::TakePlatformFile() { + PlatformFile file = file_; + file_ = kInvalidPlatformFileValue; + return file; +} + +void File::Close() { + base::ThreadRestrictions::AssertIOAllowed(); + if (!IsValid()) + return; + + if (!IGNORE_EINTR(close(file_))) + file_ = kInvalidPlatformFileValue; +} + +int64 File::Seek(Whence whence, int64 offset) { + base::ThreadRestrictions::AssertIOAllowed(); + DCHECK(IsValid()); + if (file_ < 0 || offset < 0) + return -1; + + return lseek(file_, static_cast<off_t>(offset), static_cast<int>(whence)); +} + +int File::Read(int64 offset, char* data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + DCHECK(IsValid()); + if (size < 0) + return -1; + + int bytes_read = 0; + int rv; + do { + rv = HANDLE_EINTR(pread(file_, data + bytes_read, + size - bytes_read, offset + bytes_read)); + if (rv <= 0) + break; + + bytes_read += rv; + } while (bytes_read < size); + + return bytes_read ? bytes_read : rv; +} + +int File::ReadAtCurrentPos(char* data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + DCHECK(IsValid()); + if (size < 0) + return -1; + + int bytes_read = 0; + int rv; + do { + rv = HANDLE_EINTR(read(file_, data, size)); + if (rv <= 0) + break; + + bytes_read += rv; + } while (bytes_read < size); + + return bytes_read ? bytes_read : rv; +} + +int File::ReadNoBestEffort(int64 offset, char* data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + DCHECK(IsValid()); + + return HANDLE_EINTR(pread(file_, data, size, offset)); +} + +int File::ReadAtCurrentPosNoBestEffort(char* data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + DCHECK(IsValid()); + if (size < 0) + return -1; + + return HANDLE_EINTR(read(file_, data, size)); +} + +int File::Write(int64 offset, const char* data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + + if (IsOpenAppend(file_)) + return WriteAtCurrentPos(data, size); + + DCHECK(IsValid()); + if (size < 0) + return -1; + + int bytes_written = 0; + int rv; + do { + rv = HANDLE_EINTR(pwrite(file_, data + bytes_written, + size - bytes_written, offset + bytes_written)); + if (rv <= 0) + break; + + bytes_written += rv; + } while (bytes_written < size); + + return bytes_written ? bytes_written : rv; +} + +int File::WriteAtCurrentPos(const char* data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + DCHECK(IsValid()); + if (size < 0) + return -1; + + int bytes_written = 0; + int rv; + do { + rv = HANDLE_EINTR(write(file_, data, size)); + if (rv <= 0) + break; + + bytes_written += rv; + } while (bytes_written < size); + + return bytes_written ? bytes_written : rv; +} + +int File::WriteAtCurrentPosNoBestEffort(const char* data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + DCHECK(IsValid()); + if (size < 0) + return -1; + + return HANDLE_EINTR(write(file_, data, size)); +} + +bool File::Truncate(int64 length) { + base::ThreadRestrictions::AssertIOAllowed(); + DCHECK(IsValid()); + return !CallFtruncate(file_, length); +} + +bool File::Flush() { + base::ThreadRestrictions::AssertIOAllowed(); + DCHECK(IsValid()); + return !CallFsync(file_); +} + +bool File::SetTimes(Time last_access_time, Time last_modified_time) { + base::ThreadRestrictions::AssertIOAllowed(); + DCHECK(IsValid()); + + timeval times[2]; + times[0] = last_access_time.ToTimeVal(); + times[1] = last_modified_time.ToTimeVal(); + + return !CallFutimes(file_, times); +} + +bool File::GetInfo(Info* info) { + DCHECK(IsValid()); + + stat_wrapper_t file_info; + if (CallFstat(file_, &file_info)) + return false; + + info->is_directory = S_ISDIR(file_info.st_mode); + info->is_symbolic_link = S_ISLNK(file_info.st_mode); + info->size = file_info.st_size; + +#if defined(OS_LINUX) + const time_t last_modified_sec = file_info.st_mtim.tv_sec; + const int64 last_modified_nsec = file_info.st_mtim.tv_nsec; + const time_t last_accessed_sec = file_info.st_atim.tv_sec; + const int64 last_accessed_nsec = file_info.st_atim.tv_nsec; + const time_t creation_time_sec = file_info.st_ctim.tv_sec; + const int64 creation_time_nsec = file_info.st_ctim.tv_nsec; +#elif defined(OS_ANDROID) + const time_t last_modified_sec = file_info.st_mtime; + const int64 last_modified_nsec = file_info.st_mtime_nsec; + const time_t last_accessed_sec = file_info.st_atime; + const int64 last_accessed_nsec = file_info.st_atime_nsec; + const time_t creation_time_sec = file_info.st_ctime; + const int64 creation_time_nsec = file_info.st_ctime_nsec; +#elif defined(OS_MACOSX) || defined(OS_IOS) || defined(OS_BSD) + const time_t last_modified_sec = file_info.st_mtimespec.tv_sec; + const int64 last_modified_nsec = file_info.st_mtimespec.tv_nsec; + const time_t last_accessed_sec = file_info.st_atimespec.tv_sec; + const int64 last_accessed_nsec = file_info.st_atimespec.tv_nsec; + const time_t creation_time_sec = file_info.st_ctimespec.tv_sec; + const int64 creation_time_nsec = file_info.st_ctimespec.tv_nsec; +#else + // TODO(gavinp): Investigate a good high resolution option for OS_NACL. + const time_t last_modified_sec = file_info.st_mtime; + const int64 last_modified_nsec = 0; + const time_t last_accessed_sec = file_info.st_atime; + const int64 last_accessed_nsec = 0; + const time_t creation_time_sec = file_info.st_ctime; + const int64 creation_time_nsec = 0; +#endif + + info->last_modified = + base::Time::FromTimeT(last_modified_sec) + + base::TimeDelta::FromMicroseconds(last_modified_nsec / + base::Time::kNanosecondsPerMicrosecond); + info->last_accessed = + base::Time::FromTimeT(last_accessed_sec) + + base::TimeDelta::FromMicroseconds(last_accessed_nsec / + base::Time::kNanosecondsPerMicrosecond); + info->creation_time = + base::Time::FromTimeT(creation_time_sec) + + base::TimeDelta::FromMicroseconds(creation_time_nsec / + base::Time::kNanosecondsPerMicrosecond); + return true; +} + +File::Error File::Lock() { + return CallFctnlFlock(file_, true); +} + +File::Error File::Unlock() { + return CallFctnlFlock(file_, false); +} + +// Static. +File::Error File::OSErrorToFileError(int saved_errno) { + switch (saved_errno) { + case EACCES: + case EISDIR: + case EROFS: + case EPERM: + return FILE_ERROR_ACCESS_DENIED; +#if !defined(OS_NACL) // ETXTBSY not defined by NaCl. + case ETXTBSY: + return FILE_ERROR_IN_USE; +#endif + case EEXIST: + return FILE_ERROR_EXISTS; + case ENOENT: + return FILE_ERROR_NOT_FOUND; + case EMFILE: + return FILE_ERROR_TOO_MANY_OPENED; + case ENOMEM: + return FILE_ERROR_NO_MEMORY; + case ENOSPC: + return FILE_ERROR_NO_SPACE; + case ENOTDIR: + return FILE_ERROR_NOT_A_DIRECTORY; + default: +#if !defined(OS_NACL) // NaCl build has no metrics code. + UMA_HISTOGRAM_SPARSE_SLOWLY("PlatformFile.UnknownErrors.Posix", + saved_errno); +#endif + return FILE_ERROR_FAILED; + } +} + +void File::SetPlatformFile(PlatformFile file) { + DCHECK_EQ(file_, kInvalidPlatformFileValue); + file_ = file; +} + +} // namespace base diff --git a/chromium/base/files/file_unittest.cc b/chromium/base/files/file_unittest.cc new file mode 100644 index 00000000000..b2e855da1a0 --- /dev/null +++ b/chromium/base/files/file_unittest.cc @@ -0,0 +1,360 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/file_util.h" +#include "base/files/file.h" +#include "base/files/scoped_temp_dir.h" +#include "base/time/time.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::File; +using base::FilePath; + +TEST(File, Create) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + FilePath file_path = temp_dir.path().AppendASCII("create_file_1"); + + { + // Open a file that doesn't exist. + File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ); + EXPECT_FALSE(file.IsValid()); + EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND, file.error()); + } + + { + // Open or create a file. + File file(file_path, base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_READ); + EXPECT_TRUE(file.IsValid()); + EXPECT_TRUE(file.created()); + EXPECT_EQ(base::File::FILE_OK, file.error()); + } + + { + // Open an existing file. + File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ); + EXPECT_TRUE(file.IsValid()); + EXPECT_FALSE(file.created()); + EXPECT_EQ(base::File::FILE_OK, file.error()); + + // This time verify closing the file. + file.Close(); + EXPECT_FALSE(file.IsValid()); + } + + { + // Create a file that exists. + File file(file_path, base::File::FLAG_CREATE | base::File::FLAG_READ); + EXPECT_FALSE(file.IsValid()); + EXPECT_FALSE(file.created()); + EXPECT_EQ(base::File::FILE_ERROR_EXISTS, file.error()); + } + + { + // Create or overwrite a file. + File file(file_path, + base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_READ); + EXPECT_TRUE(file.IsValid()); + EXPECT_TRUE(file.created()); + EXPECT_EQ(base::File::FILE_OK, file.error()); + } + + { + // Create a delete-on-close file. + file_path = temp_dir.path().AppendASCII("create_file_2"); + File file(file_path, + base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_READ | + base::File::FLAG_DELETE_ON_CLOSE); + EXPECT_TRUE(file.IsValid()); + EXPECT_TRUE(file.created()); + EXPECT_EQ(base::File::FILE_OK, file.error()); + } + + EXPECT_FALSE(base::PathExists(file_path)); +} + +TEST(File, DeleteOpenFile) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + FilePath file_path = temp_dir.path().AppendASCII("create_file_1"); + + // Create a file. + File file(file_path, + base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_READ | + base::File::FLAG_SHARE_DELETE); + EXPECT_TRUE(file.IsValid()); + EXPECT_TRUE(file.created()); + EXPECT_EQ(base::File::FILE_OK, file.error()); + + // Open an existing file and mark it as delete on close. + File same_file(file_path, + base::File::FLAG_OPEN | base::File::FLAG_DELETE_ON_CLOSE | + base::File::FLAG_READ); + EXPECT_TRUE(file.IsValid()); + EXPECT_FALSE(same_file.created()); + EXPECT_EQ(base::File::FILE_OK, same_file.error()); + + // Close both handles and check that the file is gone. + file.Close(); + same_file.Close(); + EXPECT_FALSE(base::PathExists(file_path)); +} + +TEST(File, ReadWrite) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + FilePath file_path = temp_dir.path().AppendASCII("read_write_file"); + File file(file_path, + base::File::FLAG_CREATE | base::File::FLAG_READ | + base::File::FLAG_WRITE); + ASSERT_TRUE(file.IsValid()); + + char data_to_write[] = "test"; + const int kTestDataSize = 4; + + // Write 0 bytes to the file. + int bytes_written = file.Write(0, data_to_write, 0); + EXPECT_EQ(0, bytes_written); + + // Write "test" to the file. + bytes_written = file.Write(0, data_to_write, kTestDataSize); + EXPECT_EQ(kTestDataSize, bytes_written); + + // Read from EOF. + char data_read_1[32]; + int bytes_read = file.Read(kTestDataSize, data_read_1, kTestDataSize); + EXPECT_EQ(0, bytes_read); + + // Read from somewhere in the middle of the file. + const int kPartialReadOffset = 1; + bytes_read = file.Read(kPartialReadOffset, data_read_1, kTestDataSize); + EXPECT_EQ(kTestDataSize - kPartialReadOffset, bytes_read); + for (int i = 0; i < bytes_read; i++) + EXPECT_EQ(data_to_write[i + kPartialReadOffset], data_read_1[i]); + + // Read 0 bytes. + bytes_read = file.Read(0, data_read_1, 0); + EXPECT_EQ(0, bytes_read); + + // Read the entire file. + bytes_read = file.Read(0, data_read_1, kTestDataSize); + EXPECT_EQ(kTestDataSize, bytes_read); + for (int i = 0; i < bytes_read; i++) + EXPECT_EQ(data_to_write[i], data_read_1[i]); + + // Read again, but using the trivial native wrapper. + bytes_read = file.ReadNoBestEffort(0, data_read_1, kTestDataSize); + EXPECT_LE(bytes_read, kTestDataSize); + for (int i = 0; i < bytes_read; i++) + EXPECT_EQ(data_to_write[i], data_read_1[i]); + + // Write past the end of the file. + const int kOffsetBeyondEndOfFile = 10; + const int kPartialWriteLength = 2; + bytes_written = file.Write(kOffsetBeyondEndOfFile, + data_to_write, kPartialWriteLength); + EXPECT_EQ(kPartialWriteLength, bytes_written); + + // Make sure the file was extended. + int64 file_size = 0; + EXPECT_TRUE(GetFileSize(file_path, &file_size)); + EXPECT_EQ(kOffsetBeyondEndOfFile + kPartialWriteLength, file_size); + + // Make sure the file was zero-padded. + char data_read_2[32]; + bytes_read = file.Read(0, data_read_2, static_cast<int>(file_size)); + EXPECT_EQ(file_size, bytes_read); + for (int i = 0; i < kTestDataSize; i++) + EXPECT_EQ(data_to_write[i], data_read_2[i]); + for (int i = kTestDataSize; i < kOffsetBeyondEndOfFile; i++) + EXPECT_EQ(0, data_read_2[i]); + for (int i = kOffsetBeyondEndOfFile; i < file_size; i++) + EXPECT_EQ(data_to_write[i - kOffsetBeyondEndOfFile], data_read_2[i]); +} + +TEST(File, Append) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + FilePath file_path = temp_dir.path().AppendASCII("append_file"); + File file(file_path, base::File::FLAG_CREATE | base::File::FLAG_APPEND); + ASSERT_TRUE(file.IsValid()); + + char data_to_write[] = "test"; + const int kTestDataSize = 4; + + // Write 0 bytes to the file. + int bytes_written = file.Write(0, data_to_write, 0); + EXPECT_EQ(0, bytes_written); + + // Write "test" to the file. + bytes_written = file.Write(0, data_to_write, kTestDataSize); + EXPECT_EQ(kTestDataSize, bytes_written); + + file.Close(); + File file2(file_path, + base::File::FLAG_OPEN | base::File::FLAG_READ | + base::File::FLAG_APPEND); + ASSERT_TRUE(file2.IsValid()); + + // Test passing the file around. + file = file2.Pass(); + EXPECT_FALSE(file2.IsValid()); + ASSERT_TRUE(file.IsValid()); + + char append_data_to_write[] = "78"; + const int kAppendDataSize = 2; + + // Append "78" to the file. + bytes_written = file.Write(0, append_data_to_write, kAppendDataSize); + EXPECT_EQ(kAppendDataSize, bytes_written); + + // Read the entire file. + char data_read_1[32]; + int bytes_read = file.Read(0, data_read_1, + kTestDataSize + kAppendDataSize); + EXPECT_EQ(kTestDataSize + kAppendDataSize, bytes_read); + for (int i = 0; i < kTestDataSize; i++) + EXPECT_EQ(data_to_write[i], data_read_1[i]); + for (int i = 0; i < kAppendDataSize; i++) + EXPECT_EQ(append_data_to_write[i], data_read_1[kTestDataSize + i]); +} + + +TEST(File, Truncate) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + FilePath file_path = temp_dir.path().AppendASCII("truncate_file"); + File file(file_path, + base::File::FLAG_CREATE | base::File::FLAG_READ | + base::File::FLAG_WRITE); + ASSERT_TRUE(file.IsValid()); + + // Write "test" to the file. + char data_to_write[] = "test"; + int kTestDataSize = 4; + int bytes_written = file.Write(0, data_to_write, kTestDataSize); + EXPECT_EQ(kTestDataSize, bytes_written); + + // Extend the file. + const int kExtendedFileLength = 10; + int64 file_size = 0; + EXPECT_TRUE(file.Truncate(kExtendedFileLength)); + EXPECT_TRUE(GetFileSize(file_path, &file_size)); + EXPECT_EQ(kExtendedFileLength, file_size); + + // Make sure the file was zero-padded. + char data_read[32]; + int bytes_read = file.Read(0, data_read, static_cast<int>(file_size)); + EXPECT_EQ(file_size, bytes_read); + for (int i = 0; i < kTestDataSize; i++) + EXPECT_EQ(data_to_write[i], data_read[i]); + for (int i = kTestDataSize; i < file_size; i++) + EXPECT_EQ(0, data_read[i]); + + // Truncate the file. + const int kTruncatedFileLength = 2; + EXPECT_TRUE(file.Truncate(kTruncatedFileLength)); + EXPECT_TRUE(GetFileSize(file_path, &file_size)); + EXPECT_EQ(kTruncatedFileLength, file_size); + + // Make sure the file was truncated. + bytes_read = file.Read(0, data_read, kTestDataSize); + EXPECT_EQ(file_size, bytes_read); + for (int i = 0; i < file_size; i++) + EXPECT_EQ(data_to_write[i], data_read[i]); +} + +// Flakily fails: http://crbug.com/86494 +#if defined(OS_ANDROID) +TEST(File, TouchGetInfo) { +#else +TEST(File, DISABLED_TouchGetInfo) { +#endif + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + File file(temp_dir.path().AppendASCII("touch_get_info_file"), + base::File::FLAG_CREATE | base::File::FLAG_WRITE | + base::File::FLAG_WRITE_ATTRIBUTES); + ASSERT_TRUE(file.IsValid()); + + // Get info for a newly created file. + base::File::Info info; + EXPECT_TRUE(file.GetInfo(&info)); + + // Add 2 seconds to account for possible rounding errors on + // filesystems that use a 1s or 2s timestamp granularity. + base::Time now = base::Time::Now() + base::TimeDelta::FromSeconds(2); + EXPECT_EQ(0, info.size); + EXPECT_FALSE(info.is_directory); + EXPECT_FALSE(info.is_symbolic_link); + EXPECT_LE(info.last_accessed.ToInternalValue(), now.ToInternalValue()); + EXPECT_LE(info.last_modified.ToInternalValue(), now.ToInternalValue()); + EXPECT_LE(info.creation_time.ToInternalValue(), now.ToInternalValue()); + base::Time creation_time = info.creation_time; + + // Write "test" to the file. + char data[] = "test"; + const int kTestDataSize = 4; + int bytes_written = file.Write(0, data, kTestDataSize); + EXPECT_EQ(kTestDataSize, bytes_written); + + // Change the last_accessed and last_modified dates. + // It's best to add values that are multiples of 2 (in seconds) + // to the current last_accessed and last_modified times, because + // FATxx uses a 2s timestamp granularity. + base::Time new_last_accessed = + info.last_accessed + base::TimeDelta::FromSeconds(234); + base::Time new_last_modified = + info.last_modified + base::TimeDelta::FromMinutes(567); + + EXPECT_TRUE(file.SetTimes(new_last_accessed, new_last_modified)); + + // Make sure the file info was updated accordingly. + EXPECT_TRUE(file.GetInfo(&info)); + EXPECT_EQ(info.size, kTestDataSize); + EXPECT_FALSE(info.is_directory); + EXPECT_FALSE(info.is_symbolic_link); + + // ext2/ext3 and HPS/HPS+ seem to have a timestamp granularity of 1s. +#if defined(OS_POSIX) + EXPECT_EQ(info.last_accessed.ToTimeVal().tv_sec, + new_last_accessed.ToTimeVal().tv_sec); + EXPECT_EQ(info.last_modified.ToTimeVal().tv_sec, + new_last_modified.ToTimeVal().tv_sec); +#else + EXPECT_EQ(info.last_accessed.ToInternalValue(), + new_last_accessed.ToInternalValue()); + EXPECT_EQ(info.last_modified.ToInternalValue(), + new_last_modified.ToInternalValue()); +#endif + + EXPECT_EQ(info.creation_time.ToInternalValue(), + creation_time.ToInternalValue()); +} + +TEST(File, ReadFileAtCurrentPosition) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + FilePath file_path = + temp_dir.path().AppendASCII("read_file_at_current_position"); + File file(file_path, + base::File::FLAG_CREATE | base::File::FLAG_READ | + base::File::FLAG_WRITE); + EXPECT_TRUE(file.IsValid()); + + const char kData[] = "test"; + const int kDataSize = arraysize(kData) - 1; + EXPECT_EQ(kDataSize, file.Write(0, kData, kDataSize)); + + EXPECT_EQ(0, file.Seek(base::File::FROM_BEGIN, 0)); + + char buffer[kDataSize]; + int first_chunk_size = kDataSize / 2; + EXPECT_EQ(first_chunk_size, file.ReadAtCurrentPos(buffer, first_chunk_size)); + EXPECT_EQ(kDataSize - first_chunk_size, + file.ReadAtCurrentPos(buffer + first_chunk_size, + kDataSize - first_chunk_size)); + EXPECT_EQ(std::string(buffer, buffer + kDataSize), + std::string(kData)); +} diff --git a/chromium/base/files/file_util_proxy.cc b/chromium/base/files/file_util_proxy.cc index 5f6d405a905..40cac112820 100644 --- a/chromium/base/files/file_util_proxy.cc +++ b/chromium/base/files/file_util_proxy.cc @@ -76,7 +76,7 @@ class CreateTemporaryHelper { void RunWork(int additional_file_flags) { // TODO(darin): file_util should have a variant of CreateTemporaryFile // that returns a FilePath and a PlatformFile. - file_util::CreateTemporaryFile(&file_path_); + base::CreateTemporaryFile(&file_path_); int file_flags = PLATFORM_FILE_WRITE | @@ -111,7 +111,7 @@ class GetFileInfoHelper { error_ = PLATFORM_FILE_ERROR_NOT_FOUND; return; } - if (!file_util::GetFileInfo(file_path, &file_info_)) + if (!GetFileInfo(file_path, &file_info_)) error_ = PLATFORM_FILE_ERROR_FAILED; } @@ -213,7 +213,7 @@ PlatformFileError DeleteAdapter(const FilePath& file_path, bool recursive) { return PLATFORM_FILE_ERROR_NOT_FOUND; } if (!base::DeleteFile(file_path, recursive)) { - if (!recursive && !file_util::IsDirectoryEmpty(file_path)) { + if (!recursive && !base::IsDirectoryEmpty(file_path)) { return PLATFORM_FILE_ERROR_NOT_EMPTY; } return PLATFORM_FILE_ERROR_FAILED; @@ -357,8 +357,7 @@ bool FileUtilProxy::Touch( return base::PostTaskAndReplyWithResult( task_runner, FROM_HERE, - Bind(&file_util::TouchFile, file_path, - last_access_time, last_modified_time), + Bind(&TouchFile, file_path, last_access_time, last_modified_time), Bind(&CallWithTranslatedParameter, callback)); } diff --git a/chromium/base/files/file_util_proxy_unittest.cc b/chromium/base/files/file_util_proxy_unittest.cc index 73ac8a60474..17c7a3f7cf5 100644 --- a/chromium/base/files/file_util_proxy_unittest.cc +++ b/chromium/base/files/file_util_proxy_unittest.cc @@ -226,7 +226,7 @@ TEST_F(FileUtilProxyTest, GetFileInfo_File) { // Setup. ASSERT_EQ(4, file_util::WriteFile(test_path(), "test", 4)); PlatformFileInfo expected_info; - file_util::GetFileInfo(test_path(), &expected_info); + GetFileInfo(test_path(), &expected_info); // Run. FileUtilProxy::GetFileInfo( @@ -247,9 +247,9 @@ TEST_F(FileUtilProxyTest, GetFileInfo_File) { TEST_F(FileUtilProxyTest, GetFileInfo_Directory) { // Setup. - ASSERT_TRUE(file_util::CreateDirectory(test_path())); + ASSERT_TRUE(base::CreateDirectory(test_path())); PlatformFileInfo expected_info; - file_util::GetFileInfo(test_path(), &expected_info); + GetFileInfo(test_path(), &expected_info); // Run. FileUtilProxy::GetFileInfo( @@ -320,7 +320,7 @@ TEST_F(FileUtilProxyTest, WriteAndFlush) { // Verify the written data. char buffer[10]; - EXPECT_EQ(data_bytes, file_util::ReadFile(test_path(), buffer, data_bytes)); + EXPECT_EQ(data_bytes, base::ReadFile(test_path(), buffer, data_bytes)); for (int i = 0; i < data_bytes; ++i) { EXPECT_EQ(data[i], buffer[i]); } @@ -342,7 +342,7 @@ TEST_F(FileUtilProxyTest, Touch) { EXPECT_EQ(PLATFORM_FILE_OK, error_); PlatformFileInfo info; - file_util::GetFileInfo(test_path(), &info); + GetFileInfo(test_path(), &info); // The returned values may only have the seconds precision, so we cast // the double values to int here. @@ -357,7 +357,7 @@ TEST_F(FileUtilProxyTest, Truncate_Shrink) { const char kTestData[] = "0123456789"; ASSERT_EQ(10, file_util::WriteFile(test_path(), kTestData, 10)); PlatformFileInfo info; - file_util::GetFileInfo(test_path(), &info); + GetFileInfo(test_path(), &info); ASSERT_EQ(10, info.size); // Run. @@ -369,11 +369,11 @@ TEST_F(FileUtilProxyTest, Truncate_Shrink) { MessageLoop::current()->Run(); // Verify. - file_util::GetFileInfo(test_path(), &info); + GetFileInfo(test_path(), &info); ASSERT_EQ(7, info.size); char buffer[7]; - EXPECT_EQ(7, file_util::ReadFile(test_path(), buffer, 7)); + EXPECT_EQ(7, base::ReadFile(test_path(), buffer, 7)); int i = 0; for (; i < 7; ++i) EXPECT_EQ(kTestData[i], buffer[i]); @@ -384,7 +384,7 @@ TEST_F(FileUtilProxyTest, Truncate_Expand) { const char kTestData[] = "9876543210"; ASSERT_EQ(10, file_util::WriteFile(test_path(), kTestData, 10)); PlatformFileInfo info; - file_util::GetFileInfo(test_path(), &info); + GetFileInfo(test_path(), &info); ASSERT_EQ(10, info.size); // Run. @@ -396,11 +396,11 @@ TEST_F(FileUtilProxyTest, Truncate_Expand) { MessageLoop::current()->Run(); // Verify. - file_util::GetFileInfo(test_path(), &info); + GetFileInfo(test_path(), &info); ASSERT_EQ(53, info.size); char buffer[53]; - EXPECT_EQ(53, file_util::ReadFile(test_path(), buffer, 53)); + EXPECT_EQ(53, base::ReadFile(test_path(), buffer, 53)); int i = 0; for (; i < 10; ++i) EXPECT_EQ(kTestData[i], buffer[i]); diff --git a/chromium/base/files/file_win.cc b/chromium/base/files/file_win.cc new file mode 100644 index 00000000000..94f4d7f59c9 --- /dev/null +++ b/chromium/base/files/file_win.cc @@ -0,0 +1,319 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/files/file.h" + +#include <io.h> + +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/metrics/sparse_histogram.h" +#include "base/threading/thread_restrictions.h" + +namespace base { + +void File::CreateBaseFileUnsafe(const FilePath& name, uint32 flags) { + base::ThreadRestrictions::AssertIOAllowed(); + DCHECK(!IsValid()); + + DWORD disposition = 0; + + if (flags & FLAG_OPEN) + disposition = OPEN_EXISTING; + + if (flags & FLAG_CREATE) { + DCHECK(!disposition); + disposition = CREATE_NEW; + } + + if (flags & FLAG_OPEN_ALWAYS) { + DCHECK(!disposition); + disposition = OPEN_ALWAYS; + } + + if (flags & FLAG_CREATE_ALWAYS) { + DCHECK(!disposition); + disposition = CREATE_ALWAYS; + } + + if (flags & FLAG_OPEN_TRUNCATED) { + DCHECK(!disposition); + DCHECK(flags & FLAG_WRITE); + disposition = TRUNCATE_EXISTING; + } + + if (!disposition) { + NOTREACHED(); + return; + } + + DWORD access = 0; + if (flags & FLAG_WRITE) + access = GENERIC_WRITE; + if (flags & FLAG_APPEND) { + DCHECK(!access); + access = FILE_APPEND_DATA; + } + if (flags & FLAG_READ) + access |= GENERIC_READ; + if (flags & FLAG_WRITE_ATTRIBUTES) + access |= FILE_WRITE_ATTRIBUTES; + if (flags & FLAG_EXECUTE) + access |= GENERIC_EXECUTE; + + DWORD sharing = (flags & FLAG_EXCLUSIVE_READ) ? 0 : FILE_SHARE_READ; + if (!(flags & FLAG_EXCLUSIVE_WRITE)) + sharing |= FILE_SHARE_WRITE; + if (flags & FLAG_SHARE_DELETE) + sharing |= FILE_SHARE_DELETE; + + DWORD create_flags = 0; + if (flags & FLAG_ASYNC) + create_flags |= FILE_FLAG_OVERLAPPED; + if (flags & FLAG_TEMPORARY) + create_flags |= FILE_ATTRIBUTE_TEMPORARY; + if (flags & FLAG_HIDDEN) + create_flags |= FILE_ATTRIBUTE_HIDDEN; + if (flags & FLAG_DELETE_ON_CLOSE) + create_flags |= FILE_FLAG_DELETE_ON_CLOSE; + if (flags & FLAG_BACKUP_SEMANTICS) + create_flags |= FILE_FLAG_BACKUP_SEMANTICS; + + file_.Set(CreateFile(name.value().c_str(), access, sharing, NULL, + disposition, create_flags, NULL)); + + if (file_.IsValid()) { + error_ = FILE_OK; + async_ = ((flags & FLAG_ASYNC) == FLAG_ASYNC); + + if (flags & (FLAG_OPEN_ALWAYS)) + created_ = (ERROR_ALREADY_EXISTS != GetLastError()); + else if (flags & (FLAG_CREATE_ALWAYS | FLAG_CREATE)) + created_ = true; + } else { + error_ = OSErrorToFileError(GetLastError()); + } +} + +bool File::IsValid() const { + return file_.IsValid(); +} +PlatformFile File::TakePlatformFile() { + return file_.Take(); +} + +void File::Close() { + base::ThreadRestrictions::AssertIOAllowed(); + file_.Close(); +} + +int64 File::Seek(Whence whence, int64 offset) { + base::ThreadRestrictions::AssertIOAllowed(); + DCHECK(IsValid()); + if (offset < 0) + return -1; + + LARGE_INTEGER distance, res; + distance.QuadPart = offset; + DWORD move_method = static_cast<DWORD>(whence); + if (!SetFilePointerEx(file_, distance, &res, move_method)) + return -1; + return res.QuadPart; +} + +int File::Read(int64 offset, char* data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + DCHECK(IsValid()); + DCHECK(!async_); + if (size < 0) + return -1; + + LARGE_INTEGER offset_li; + offset_li.QuadPart = offset; + + OVERLAPPED overlapped = {0}; + overlapped.Offset = offset_li.LowPart; + overlapped.OffsetHigh = offset_li.HighPart; + + DWORD bytes_read; + if (::ReadFile(file_, data, size, &bytes_read, &overlapped) != 0) + return bytes_read; + if (ERROR_HANDLE_EOF == GetLastError()) + return 0; + + return -1; +} + +int File::ReadAtCurrentPos(char* data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + DCHECK(IsValid()); + DCHECK(!async_); + if (size < 0) + return -1; + + DWORD bytes_read; + if (::ReadFile(file_, data, size, &bytes_read, NULL) != 0) + return bytes_read; + if (ERROR_HANDLE_EOF == GetLastError()) + return 0; + + return -1; +} + +int File::ReadNoBestEffort(int64 offset, char* data, int size) { + return Read(offset, data, size); +} + +int File::ReadAtCurrentPosNoBestEffort(char* data, int size) { + return ReadAtCurrentPos(data, size); +} + +int File::Write(int64 offset, const char* data, int size) { + base::ThreadRestrictions::AssertIOAllowed(); + DCHECK(IsValid()); + DCHECK(!async_); + + LARGE_INTEGER offset_li; + offset_li.QuadPart = offset; + + OVERLAPPED overlapped = {0}; + overlapped.Offset = offset_li.LowPart; + overlapped.OffsetHigh = offset_li.HighPart; + + DWORD bytes_written; + if (::WriteFile(file_, data, size, &bytes_written, &overlapped) != 0) + return bytes_written; + + return -1; +} + +int File::WriteAtCurrentPos(const char* data, int size) { + NOTREACHED(); + return -1; +} + +int File::WriteAtCurrentPosNoBestEffort(const char* data, int size) { + return WriteAtCurrentPos(data, size); +} + +bool File::Truncate(int64 length) { + base::ThreadRestrictions::AssertIOAllowed(); + DCHECK(IsValid()); + + // Get the current file pointer. + LARGE_INTEGER file_pointer; + LARGE_INTEGER zero; + zero.QuadPart = 0; + if (::SetFilePointerEx(file_, zero, &file_pointer, FILE_CURRENT) == 0) + return false; + + LARGE_INTEGER length_li; + length_li.QuadPart = length; + // If length > file size, SetFilePointerEx() should extend the file + // with zeroes on all Windows standard file systems (NTFS, FATxx). + if (!::SetFilePointerEx(file_, length_li, NULL, FILE_BEGIN)) + return false; + + // Set the new file length and move the file pointer to its old position. + // This is consistent with ftruncate()'s behavior, even when the file + // pointer points to a location beyond the end of the file. + return ((::SetEndOfFile(file_) != 0) && + (::SetFilePointerEx(file_, file_pointer, NULL, FILE_BEGIN) != 0)); +} + +bool File::Flush() { + base::ThreadRestrictions::AssertIOAllowed(); + DCHECK(IsValid()); + return ::FlushFileBuffers(file_) != FALSE; +} + +bool File::SetTimes(Time last_access_time, Time last_modified_time) { + base::ThreadRestrictions::AssertIOAllowed(); + DCHECK(IsValid()); + + FILETIME last_access_filetime = last_access_time.ToFileTime(); + FILETIME last_modified_filetime = last_modified_time.ToFileTime(); + return (::SetFileTime(file_, NULL, &last_access_filetime, + &last_modified_filetime) != 0); +} + +bool File::GetInfo(Info* info) { + base::ThreadRestrictions::AssertIOAllowed(); + DCHECK(IsValid()); + + BY_HANDLE_FILE_INFORMATION file_info; + if (GetFileInformationByHandle(file_, &file_info) == 0) + return false; + + LARGE_INTEGER size; + size.HighPart = file_info.nFileSizeHigh; + size.LowPart = file_info.nFileSizeLow; + info->size = size.QuadPart; + info->is_directory = + (file_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; + info->is_symbolic_link = false; // Windows doesn't have symbolic links. + info->last_modified = base::Time::FromFileTime(file_info.ftLastWriteTime); + info->last_accessed = base::Time::FromFileTime(file_info.ftLastAccessTime); + info->creation_time = base::Time::FromFileTime(file_info.ftCreationTime); + return true; +} + +File::Error base::File::Lock() { + DCHECK(IsValid()); + BOOL result = LockFile(file_, 0, 0, MAXDWORD, MAXDWORD); + if (!result) + return OSErrorToFileError(GetLastError()); + return FILE_OK; +} + +File::Error File::Unlock() { + DCHECK(IsValid()); + BOOL result = UnlockFile(file_, 0, 0, MAXDWORD, MAXDWORD); + if (!result) + return OSErrorToFileError(GetLastError()); + return FILE_OK; +} + +// Static. +File::Error File::OSErrorToFileError(DWORD last_error) { + switch (last_error) { + case ERROR_SHARING_VIOLATION: + return FILE_ERROR_IN_USE; + case ERROR_FILE_EXISTS: + return FILE_ERROR_EXISTS; + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + return FILE_ERROR_NOT_FOUND; + case ERROR_ACCESS_DENIED: + return FILE_ERROR_ACCESS_DENIED; + case ERROR_TOO_MANY_OPEN_FILES: + return FILE_ERROR_TOO_MANY_OPENED; + case ERROR_OUTOFMEMORY: + case ERROR_NOT_ENOUGH_MEMORY: + return FILE_ERROR_NO_MEMORY; + case ERROR_HANDLE_DISK_FULL: + case ERROR_DISK_FULL: + case ERROR_DISK_RESOURCES_EXHAUSTED: + return FILE_ERROR_NO_SPACE; + case ERROR_USER_MAPPED_FILE: + return FILE_ERROR_INVALID_OPERATION; + case ERROR_NOT_READY: + case ERROR_SECTOR_NOT_FOUND: + case ERROR_DEV_NOT_EXIST: + case ERROR_IO_DEVICE: + case ERROR_FILE_CORRUPT: + case ERROR_DISK_CORRUPT: + return FILE_ERROR_IO; + default: + UMA_HISTOGRAM_SPARSE_SLOWLY("PlatformFile.UnknownErrors.Windows", + last_error); + return FILE_ERROR_FAILED; + } +} + +void File::SetPlatformFile(PlatformFile file) { + file_.Set(file); +} + +} // namespace base diff --git a/chromium/base/files/important_file_writer.cc b/chromium/base/files/important_file_writer.cc index d88498506b4..261c98772e5 100644 --- a/chromium/base/files/important_file_writer.cc +++ b/chromium/base/files/important_file_writer.cc @@ -2,6 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#if defined _MSC_VER && _MSC_VER == 1800 +// TODO(scottmg): Internal errors on VS2013 RC in LTCG. This should be removed +// after RTM. http://crbug.com/288948 +#pragma optimize("", off) +#endif + #include "base/files/important_file_writer.h" #include <stdio.h> @@ -52,7 +58,7 @@ bool ImportantFileWriter::WriteFileAtomically(const FilePath& path, // as target file, so it can be moved in one step, and that the temp file // is securely created. FilePath tmp_file_path; - if (!file_util::CreateTemporaryFileInDir(path.DirName(), &tmp_file_path)) { + if (!base::CreateTemporaryFileInDir(path.DirName(), &tmp_file_path)) { LogFailure(path, FAILED_CREATING, "could not create temporary file"); return false; } diff --git a/chromium/base/files/memory_mapped_file_posix.cc b/chromium/base/files/memory_mapped_file_posix.cc index ba00946722c..c4c477a3fe7 100644 --- a/chromium/base/files/memory_mapped_file_posix.cc +++ b/chromium/base/files/memory_mapped_file_posix.cc @@ -9,7 +9,6 @@ #include <unistd.h> #include "base/logging.h" -#include "base/posix/eintr_wrapper.h" #include "base/threading/thread_restrictions.h" namespace base { @@ -44,7 +43,7 @@ void MemoryMappedFile::CloseHandles() { if (data_ != NULL) munmap(data_, length_); if (file_ != kInvalidPlatformFileValue) - ignore_result(HANDLE_EINTR(close(file_))); + close(file_); data_ = NULL; length_ = 0; diff --git a/chromium/base/files/scoped_temp_dir.cc b/chromium/base/files/scoped_temp_dir.cc index 497799e9f72..b893b02ca8a 100644 --- a/chromium/base/files/scoped_temp_dir.cc +++ b/chromium/base/files/scoped_temp_dir.cc @@ -23,8 +23,7 @@ bool ScopedTempDir::CreateUniqueTempDir() { // This "scoped_dir" prefix is only used on Windows and serves as a template // for the unique name. - if (!file_util::CreateNewTempDirectory(FILE_PATH_LITERAL("scoped_dir"), - &path_)) + if (!base::CreateNewTempDirectory(FILE_PATH_LITERAL("scoped_dir"), &path_)) return false; return true; @@ -35,14 +34,13 @@ bool ScopedTempDir::CreateUniqueTempDirUnderPath(const FilePath& base_path) { return false; // If |base_path| does not exist, create it. - if (!file_util::CreateDirectory(base_path)) + if (!base::CreateDirectory(base_path)) return false; // Create a new, uniquely named directory under |base_path|. - if (!file_util::CreateTemporaryDirInDir( - base_path, - FILE_PATH_LITERAL("scoped_dir_"), - &path_)) + if (!base::CreateTemporaryDirInDir(base_path, + FILE_PATH_LITERAL("scoped_dir_"), + &path_)) return false; return true; @@ -52,7 +50,7 @@ bool ScopedTempDir::Set(const FilePath& path) { if (!path_.empty()) return false; - if (!DirectoryExists(path) && !file_util::CreateDirectory(path)) + if (!DirectoryExists(path) && !base::CreateDirectory(path)) return false; path_ = path; diff --git a/chromium/base/files/scoped_temp_dir_unittest.cc b/chromium/base/files/scoped_temp_dir_unittest.cc index 0c9131c3a8d..fe243ce2ee5 100644 --- a/chromium/base/files/scoped_temp_dir_unittest.cc +++ b/chromium/base/files/scoped_temp_dir_unittest.cc @@ -13,8 +13,8 @@ namespace base { TEST(ScopedTempDir, FullPath) { FilePath test_path; - file_util::CreateNewTempDirectory(FILE_PATH_LITERAL("scoped_temp_dir"), - &test_path); + base::CreateNewTempDirectory(FILE_PATH_LITERAL("scoped_temp_dir"), + &test_path); // Against an existing dir, it should get destroyed when leaving scope. EXPECT_TRUE(DirectoryExists(test_path)); @@ -55,7 +55,7 @@ TEST(ScopedTempDir, TempDir) { test_path = dir.path(); EXPECT_TRUE(DirectoryExists(test_path)); FilePath tmp_dir; - EXPECT_TRUE(file_util::GetTempDir(&tmp_dir)); + EXPECT_TRUE(base::GetTempDir(&tmp_dir)); EXPECT_TRUE(test_path.value().find(tmp_dir.value()) != std::string::npos); } EXPECT_FALSE(DirectoryExists(test_path)); @@ -64,8 +64,8 @@ TEST(ScopedTempDir, TempDir) { TEST(ScopedTempDir, UniqueTempDirUnderPath) { // Create a path which will contain a unique temp path. FilePath base_path; - ASSERT_TRUE(file_util::CreateNewTempDirectory(FILE_PATH_LITERAL("base_dir"), - &base_path)); + ASSERT_TRUE(base::CreateNewTempDirectory(FILE_PATH_LITERAL("base_dir"), + &base_path)); FilePath test_path; { diff --git a/chromium/base/guid.cc b/chromium/base/guid.cc index 657383fc242..b7d79f22790 100644 --- a/chromium/base/guid.cc +++ b/chromium/base/guid.cc @@ -4,9 +4,6 @@ #include "base/guid.h" -#include "base/rand_util.h" -#include "base/strings/stringprintf.h" - namespace base { bool IsValidGUID(const std::string& guid) { @@ -14,7 +11,7 @@ bool IsValidGUID(const std::string& guid) { if (guid.length() != kGUIDLength) return false; - std::string hexchars = "0123456789ABCDEF"; + const std::string hexchars = "0123456789ABCDEF"; for (uint32 i = 0; i < guid.length(); ++i) { char current = guid[i]; if (i == 8 || i == 13 || i == 18 || i == 23) { @@ -29,4 +26,4 @@ bool IsValidGUID(const std::string& guid) { return true; } -} // namespace guid +} // namespace base diff --git a/chromium/base/guid.h b/chromium/base/guid.h index 468b65ff619..2a4ed4c4b5c 100644 --- a/chromium/base/guid.h +++ b/chromium/base/guid.h @@ -27,6 +27,6 @@ BASE_EXPORT bool IsValidGUID(const std::string& guid); BASE_EXPORT std::string RandomDataToGUIDString(const uint64 bytes[2]); #endif -} // namespace guid +} // namespace base #endif // BASE_GUID_H_ diff --git a/chromium/base/i18n/break_iterator.h b/chromium/base/i18n/break_iterator.h index 96bdeaa9b34..618a320924e 100644 --- a/chromium/base/i18n/break_iterator.h +++ b/chromium/base/i18n/break_iterator.h @@ -36,7 +36,7 @@ // name (BREAK_SPACE) implied. // // Under BREAK_NEWLINE mode, all characters are included in the returned -// string, breking only when a newline-equivalent character is encountered +// string, breaking only when a newline-equivalent character is encountered // (eg. in the UTF-16 equivalent of the string "foo\nbar!\n\n", the line // breaks are at the periods in ".foo\n.bar\n.\n."). // @@ -85,11 +85,11 @@ class BASE_I18N_EXPORT BreakIterator { // Under BREAK_WORD mode, returns true if the break we just hit is the // end of a word. (Otherwise, the break iterator just skipped over e.g. // whitespace or punctuation.) Under BREAK_LINE and BREAK_NEWLINE modes, - // this distinction doesn't apply and it always retuns false. + // this distinction doesn't apply and it always returns false. bool IsWord() const; // Under BREAK_WORD mode, returns true if |position| is at the end of word or - // at the start of word. It always retuns false under BREAK_LINE and + // at the start of word. It always returns false under BREAK_LINE and // BREAK_NEWLINE modes. bool IsEndOfWord(size_t position) const; bool IsStartOfWord(size_t position) const; diff --git a/chromium/base/i18n/case_conversion_unittest.cc b/chromium/base/i18n/case_conversion_unittest.cc index 2139bbe7b38..38e2c687822 100644 --- a/chromium/base/i18n/case_conversion_unittest.cc +++ b/chromium/base/i18n/case_conversion_unittest.cc @@ -6,6 +6,7 @@ #include "base/strings/utf_string_conversions.h" #include "testing/gtest/include/gtest/gtest.h" +namespace base { namespace { // Test upper and lower case string conversion. @@ -24,3 +25,4 @@ TEST(CaseConversionTest, UpperLower) { // TODO(jshin): More tests are needed, especially with non-ASCII characters. } // namespace +} // namespace base diff --git a/chromium/base/i18n/file_util_icu.cc b/chromium/base/i18n/file_util_icu.cc index 4b2ca3ac0b2..9b0525086d0 100644 --- a/chromium/base/i18n/file_util_icu.cc +++ b/chromium/base/i18n/file_util_icu.cc @@ -19,6 +19,8 @@ #include "third_party/icu/source/common/unicode/uniset.h" #include "third_party/icu/source/i18n/unicode/coll.h" +using base::string16; + namespace { class IllegalCharacters { diff --git a/chromium/base/i18n/file_util_icu.h b/chromium/base/i18n/file_util_icu.h index 7f246c7f1c7..15526c3a645 100644 --- a/chromium/base/i18n/file_util_icu.h +++ b/chromium/base/i18n/file_util_icu.h @@ -15,7 +15,7 @@ namespace file_util { // Returns true if file_name does not have any illegal character. The input // param has the same restriction as that for ReplaceIllegalCharacters. -BASE_I18N_EXPORT bool IsFilenameLegal(const string16& file_name); +BASE_I18N_EXPORT bool IsFilenameLegal(const base::string16& file_name); // Replaces characters in 'file_name' that are illegal for file names with // 'replace_char'. 'file_name' must not be a full or relative path, but just the diff --git a/chromium/base/i18n/icu_util.cc b/chromium/base/i18n/icu_util.cc index 3e1353b499d..e5c698475e4 100644 --- a/chromium/base/i18n/icu_util.cc +++ b/chromium/base/i18n/icu_util.cc @@ -29,18 +29,6 @@ #define ICU_UTIL_DATA_SHARED 1 #define ICU_UTIL_DATA_STATIC 2 -#ifndef ICU_UTIL_DATA_IMPL - -#if defined(OS_WIN) -#define ICU_UTIL_DATA_IMPL ICU_UTIL_DATA_SHARED -#elif defined(OS_IOS) -#define ICU_UTIL_DATA_IMPL ICU_UTIL_DATA_FILE -#else -#define ICU_UTIL_DATA_IMPL ICU_UTIL_DATA_STATIC -#endif - -#endif // ICU_UTIL_DATA_IMPL - #if ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_FILE #define ICU_UTIL_DATA_FILE_NAME "icudt" U_ICU_VERSION_SHORT "l.dat" #elif ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_SHARED @@ -86,26 +74,12 @@ bool InitializeICU() { udata_setCommonData(reinterpret_cast<void*>(addr), &err); return err == U_ZERO_ERROR; #elif (ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_STATIC) - // Mac/Linux bundle the ICU data in. + // The ICU data is statically linked. return true; #elif (ICU_UTIL_DATA_IMPL == ICU_UTIL_DATA_FILE) -#if !defined(OS_MACOSX) - // For now, expect the data file to be alongside the executable. - // This is sufficient while we work on unit tests, but will eventually - // likely live in a data directory. - FilePath data_path; - bool path_ok = PathService::Get(base::DIR_EXE, &data_path); - DCHECK(path_ok); - u_setDataDirectory(data_path.value().c_str()); - // Only look for the packaged data file; - // the default behavior is to look for individual files. - UErrorCode err = U_ZERO_ERROR; - udata_setFileAccess(UDATA_ONLY_PACKAGES, &err); - return err == U_ZERO_ERROR; -#else // If the ICU data directory is set, ICU won't actually load the data until // it is needed. This can fail if the process is sandboxed at that time. - // Instead, Mac maps the file in and hands off the data so the sandbox won't + // Instead, we map the file in and hand off the data so the sandbox won't // cause any problems. // Chrome doesn't normally shut down ICU, so the mapped data shouldn't ever @@ -113,12 +87,22 @@ bool InitializeICU() { CR_DEFINE_STATIC_LOCAL(base::MemoryMappedFile, mapped_file, ()); if (!mapped_file.IsValid()) { // Assume it is in the framework bundle's Resources directory. +#if !defined(OS_MACOSX) + // For now, expect the data file to be alongside the executable. + // This is sufficient while we work on unit tests, but will eventually + // likely live in a data directory. + FilePath data_path; + bool path_ok = PathService::Get(base::DIR_EXE, &data_path); + DCHECK(path_ok); + data_path = data_path.AppendASCII(ICU_UTIL_DATA_FILE_NAME); +#else FilePath data_path = base::mac::PathForFrameworkBundleResource(CFSTR(ICU_UTIL_DATA_FILE_NAME)); if (data_path.empty()) { DLOG(ERROR) << ICU_UTIL_DATA_FILE_NAME << " not found in bundle"; return false; } +#endif // OS check if (!mapped_file.Initialize(data_path)) { DLOG(ERROR) << "Couldn't mmap " << data_path.value(); return false; @@ -127,7 +111,6 @@ bool InitializeICU() { UErrorCode err = U_ZERO_ERROR; udata_setCommonData(const_cast<uint8*>(mapped_file.data()), &err); return err == U_ZERO_ERROR; -#endif // OS check #endif } diff --git a/chromium/base/i18n/time_formatting.cc b/chromium/base/i18n/time_formatting.cc index 3973dd2508f..917ba43b9ec 100644 --- a/chromium/base/i18n/time_formatting.cc +++ b/chromium/base/i18n/time_formatting.cc @@ -12,8 +12,7 @@ #include "third_party/icu/source/i18n/unicode/dtptngen.h" #include "third_party/icu/source/i18n/unicode/smpdtfmt.h" -using base::Time; - +namespace base { namespace { string16 TimeFormat(const icu::DateFormat* formatter, @@ -48,8 +47,6 @@ string16 TimeFormatWithoutAmPm(const icu::DateFormat* formatter, } // namespace -namespace base { - string16 TimeFormatTimeOfDay(const Time& time) { // We can omit the locale parameter because the default should match // Chrome's application locale. diff --git a/chromium/base/i18n/timezone.cc b/chromium/base/i18n/timezone.cc new file mode 100644 index 00000000000..8c652799dbb --- /dev/null +++ b/chromium/base/i18n/timezone.cc @@ -0,0 +1,608 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/i18n/timezone.h" + +#include <map> + +#include "base/memory/singleton.h" +#include "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "third_party/icu/source/i18n/unicode/timezone.h" + +namespace base { + +namespace { + +class TimezoneMap { + public: + static TimezoneMap* GetInstance() { + return Singleton<TimezoneMap>::get(); + } + + std::string CountryCodeForTimezone(const std::string& olson_code) { + std::map<std::string, std::string>::iterator iter = map_.find(olson_code); + if (iter != map_.end()) + return iter->second; + + return std::string(); + } + + private: + TimezoneMap() { + // These mappings are adapted from zone.tab, which is available at + // <http://www.ietf.org/timezones/data/zone.tab> and is a part of public + // domain. + struct OlsonCodeData { + std::string country_code; + std::string olson_code; + } olson_code_data[] = { + { "AD", "Europe/Andorra" }, + { "AE", "Asia/Dubai" }, + { "AF", "Asia/Kabul" }, + { "AG", "America/Antigua" }, + { "AI", "America/Anguilla" }, + { "AL", "Europe/Tirane" }, + { "AM", "Asia/Yerevan" }, + { "AO", "Africa/Luanda" }, + { "AQ", "Antarctica/McMurdo" }, + { "AQ", "Antarctica/Rothera" }, + { "AQ", "Antarctica/Palmer" }, + { "AQ", "Antarctica/Mawson" }, + { "AQ", "Antarctica/Davis" }, + { "AQ", "Antarctica/Casey" }, + { "AQ", "Antarctica/Vostok" }, + { "AQ", "Antarctica/DumontDUrville" }, + { "AQ", "Antarctica/Syowa" }, + { "AR", "America/Argentina/Buenos_Aires" }, + { "AR", "America/Argentina/Cordoba" }, + { "AR", "America/Argentina/Salta" }, + { "AR", "America/Argentina/Jujuy" }, + { "AR", "America/Argentina/Tucuman" }, + { "AR", "America/Argentina/Catamarca" }, + { "AR", "America/Argentina/La_Rioja" }, + { "AR", "America/Argentina/San_Juan" }, + { "AR", "America/Argentina/Mendoza" }, + { "AR", "America/Argentina/San_Luis" }, + { "AR", "America/Argentina/Rio_Gallegos" }, + { "AR", "America/Argentina/Ushuaia" }, + { "AS", "Pacific/Pago_Pago" }, + { "AT", "Europe/Vienna" }, + { "AU", "Australia/Lord_Howe" }, + { "AU", "Antarctica/Macquarie" }, + { "AU", "Australia/Hobart" }, + { "AU", "Australia/Currie" }, + { "AU", "Australia/Melbourne" }, + { "AU", "Australia/Sydney" }, + { "AU", "Australia/Broken_Hill" }, + { "AU", "Australia/Brisbane" }, + { "AU", "Australia/Lindeman" }, + { "AU", "Australia/Adelaide" }, + { "AU", "Australia/Darwin" }, + { "AU", "Australia/Perth" }, + { "AU", "Australia/Eucla" }, + { "AW", "America/Aruba" }, + { "AX", "Europe/Mariehamn" }, + { "AZ", "Asia/Baku" }, + { "BA", "Europe/Sarajevo" }, + { "BB", "America/Barbados" }, + { "BD", "Asia/Dhaka" }, + { "BE", "Europe/Brussels" }, + { "BF", "Africa/Ouagadougou" }, + { "BG", "Europe/Sofia" }, + { "BH", "Asia/Bahrain" }, + { "BI", "Africa/Bujumbura" }, + { "BJ", "Africa/Porto-Novo" }, + { "BL", "America/St_Barthelemy" }, + { "BM", "Atlantic/Bermuda" }, + { "BN", "Asia/Brunei" }, + { "BO", "America/La_Paz" }, + { "BQ", "America/Kralendijk" }, + { "BR", "America/Noronha" }, + { "BR", "America/Belem" }, + { "BR", "America/Fortaleza" }, + { "BR", "America/Recife" }, + { "BR", "America/Araguaina" }, + { "BR", "America/Maceio" }, + { "BR", "America/Bahia" }, + { "BR", "America/Sao_Paulo" }, + { "BR", "America/Campo_Grande" }, + { "BR", "America/Cuiaba" }, + { "BR", "America/Santarem" }, + { "BR", "America/Porto_Velho" }, + { "BR", "America/Boa_Vista" }, + { "BR", "America/Manaus" }, + { "BR", "America/Eirunepe" }, + { "BR", "America/Rio_Branco" }, + { "BS", "America/Nassau" }, + { "BT", "Asia/Thimphu" }, + { "BW", "Africa/Gaborone" }, + { "BY", "Europe/Minsk" }, + { "BZ", "America/Belize" }, + { "CA", "America/St_Johns" }, + { "CA", "America/Halifax" }, + { "CA", "America/Glace_Bay" }, + { "CA", "America/Moncton" }, + { "CA", "America/Goose_Bay" }, + { "CA", "America/Blanc-Sablon" }, + { "CA", "America/Toronto" }, + { "CA", "America/Nipigon" }, + { "CA", "America/Thunder_Bay" }, + { "CA", "America/Iqaluit" }, + { "CA", "America/Pangnirtung" }, + { "CA", "America/Resolute" }, + { "CA", "America/Atikokan" }, + { "CA", "America/Rankin_Inlet" }, + { "CA", "America/Winnipeg" }, + { "CA", "America/Rainy_River" }, + { "CA", "America/Regina" }, + { "CA", "America/Swift_Current" }, + { "CA", "America/Edmonton" }, + { "CA", "America/Cambridge_Bay" }, + { "CA", "America/Yellowknife" }, + { "CA", "America/Inuvik" }, + { "CA", "America/Creston" }, + { "CA", "America/Dawson_Creek" }, + { "CA", "America/Vancouver" }, + { "CA", "America/Whitehorse" }, + { "CA", "America/Dawson" }, + { "CC", "Indian/Cocos" }, + { "CD", "Africa/Kinshasa" }, + { "CD", "Africa/Lubumbashi" }, + { "CF", "Africa/Bangui" }, + { "CG", "Africa/Brazzaville" }, + { "CH", "Europe/Zurich" }, + { "CI", "Africa/Abidjan" }, + { "CK", "Pacific/Rarotonga" }, + { "CL", "America/Santiago" }, + { "CL", "Pacific/Easter" }, + { "CM", "Africa/Douala" }, + { "CN", "Asia/Shanghai" }, + { "CN", "Asia/Harbin" }, + { "CN", "Asia/Chongqing" }, + { "CN", "Asia/Urumqi" }, + { "CN", "Asia/Kashgar" }, + { "CO", "America/Bogota" }, + { "CR", "America/Costa_Rica" }, + { "CU", "America/Havana" }, + { "CV", "Atlantic/Cape_Verde" }, + { "CW", "America/Curacao" }, + { "CX", "Indian/Christmas" }, + { "CY", "Asia/Nicosia" }, + { "CZ", "Europe/Prague" }, + { "DE", "Europe/Berlin" }, + { "DE", "Europe/Busingen" }, + { "DJ", "Africa/Djibouti" }, + { "DK", "Europe/Copenhagen" }, + { "DM", "America/Dominica" }, + { "DO", "America/Santo_Domingo" }, + { "DZ", "Africa/Algiers" }, + { "EC", "America/Guayaquil" }, + { "EC", "Pacific/Galapagos" }, + { "EE", "Europe/Tallinn" }, + { "EG", "Africa/Cairo" }, + { "EH", "Africa/El_Aaiun" }, + { "ER", "Africa/Asmara" }, + { "ES", "Europe/Madrid" }, + { "ES", "Africa/Ceuta" }, + { "ES", "Atlantic/Canary" }, + { "ET", "Africa/Addis_Ababa" }, + { "FI", "Europe/Helsinki" }, + { "FJ", "Pacific/Fiji" }, + { "FK", "Atlantic/Stanley" }, + { "FM", "Pacific/Chuuk" }, + { "FM", "Pacific/Pohnpei" }, + { "FM", "Pacific/Kosrae" }, + { "FO", "Atlantic/Faroe" }, + { "FR", "Europe/Paris" }, + { "GA", "Africa/Libreville" }, + { "GB", "Europe/London" }, + { "GD", "America/Grenada" }, + { "GE", "Asia/Tbilisi" }, + { "GF", "America/Cayenne" }, + { "GG", "Europe/Guernsey" }, + { "GH", "Africa/Accra" }, + { "GI", "Europe/Gibraltar" }, + { "GL", "America/Godthab" }, + { "GL", "America/Danmarkshavn" }, + { "GL", "America/Scoresbysund" }, + { "GL", "America/Thule" }, + { "GM", "Africa/Banjul" }, + { "GN", "Africa/Conakry" }, + { "GP", "America/Guadeloupe" }, + { "GQ", "Africa/Malabo" }, + { "GR", "Europe/Athens" }, + { "GS", "Atlantic/South_Georgia" }, + { "GT", "America/Guatemala" }, + { "GU", "Pacific/Guam" }, + { "GW", "Africa/Bissau" }, + { "GY", "America/Guyana" }, + { "HK", "Asia/Hong_Kong" }, + { "HN", "America/Tegucigalpa" }, + { "HR", "Europe/Zagreb" }, + { "HT", "America/Port-au-Prince" }, + { "HU", "Europe/Budapest" }, + { "ID", "Asia/Jakarta" }, + { "ID", "Asia/Pontianak" }, + { "ID", "Asia/Makassar" }, + { "ID", "Asia/Jayapura" }, + { "IE", "Europe/Dublin" }, + { "IL", "Asia/Jerusalem" }, + { "IM", "Europe/Isle_of_Man" }, + { "IN", "Asia/Kolkata" }, + { "IO", "Indian/Chagos" }, + { "IQ", "Asia/Baghdad" }, + { "IR", "Asia/Tehran" }, + { "IS", "Atlantic/Reykjavik" }, + { "IT", "Europe/Rome" }, + { "JE", "Europe/Jersey" }, + { "JM", "America/Jamaica" }, + { "JO", "Asia/Amman" }, + { "JP", "Asia/Tokyo" }, + { "KE", "Africa/Nairobi" }, + { "KG", "Asia/Bishkek" }, + { "KH", "Asia/Phnom_Penh" }, + { "KI", "Pacific/Tarawa" }, + { "KI", "Pacific/Enderbury" }, + { "KI", "Pacific/Kiritimati" }, + { "KM", "Indian/Comoro" }, + { "KN", "America/St_Kitts" }, + { "KP", "Asia/Pyongyang" }, + { "KR", "Asia/Seoul" }, + { "KW", "Asia/Kuwait" }, + { "KY", "America/Cayman" }, + { "KZ", "Asia/Almaty" }, + { "KZ", "Asia/Qyzylorda" }, + { "KZ", "Asia/Aqtobe" }, + { "KZ", "Asia/Aqtau" }, + { "KZ", "Asia/Oral" }, + { "LA", "Asia/Vientiane" }, + { "LB", "Asia/Beirut" }, + { "LC", "America/St_Lucia" }, + { "LI", "Europe/Vaduz" }, + { "LK", "Asia/Colombo" }, + { "LR", "Africa/Monrovia" }, + { "LS", "Africa/Maseru" }, + { "LT", "Europe/Vilnius" }, + { "LU", "Europe/Luxembourg" }, + { "LV", "Europe/Riga" }, + { "LY", "Africa/Tripoli" }, + { "MA", "Africa/Casablanca" }, + { "MC", "Europe/Monaco" }, + { "MD", "Europe/Chisinau" }, + { "ME", "Europe/Podgorica" }, + { "MF", "America/Marigot" }, + { "MG", "Indian/Antananarivo" }, + { "MH", "Pacific/Majuro" }, + { "MH", "Pacific/Kwajalein" }, + { "MK", "Europe/Skopje" }, + { "ML", "Africa/Bamako" }, + { "MM", "Asia/Rangoon" }, + { "MN", "Asia/Ulaanbaatar" }, + { "MN", "Asia/Hovd" }, + { "MN", "Asia/Choibalsan" }, + { "MO", "Asia/Macau" }, + { "MP", "Pacific/Saipan" }, + { "MQ", "America/Martinique" }, + { "MR", "Africa/Nouakchott" }, + { "MS", "America/Montserrat" }, + { "MT", "Europe/Malta" }, + { "MU", "Indian/Mauritius" }, + { "MV", "Indian/Maldives" }, + { "MW", "Africa/Blantyre" }, + { "MX", "America/Mexico_City" }, + { "MX", "America/Cancun" }, + { "MX", "America/Merida" }, + { "MX", "America/Monterrey" }, + { "MX", "America/Matamoros" }, + { "MX", "America/Mazatlan" }, + { "MX", "America/Chihuahua" }, + { "MX", "America/Ojinaga" }, + { "MX", "America/Hermosillo" }, + { "MX", "America/Tijuana" }, + { "MX", "America/Santa_Isabel" }, + { "MX", "America/Bahia_Banderas" }, + { "MY", "Asia/Kuala_Lumpur" }, + { "MY", "Asia/Kuching" }, + { "MZ", "Africa/Maputo" }, + { "NA", "Africa/Windhoek" }, + { "NC", "Pacific/Noumea" }, + { "NE", "Africa/Niamey" }, + { "NF", "Pacific/Norfolk" }, + { "NG", "Africa/Lagos" }, + { "NI", "America/Managua" }, + { "NL", "Europe/Amsterdam" }, + { "NO", "Europe/Oslo" }, + { "NP", "Asia/Kathmandu" }, + { "NR", "Pacific/Nauru" }, + { "NU", "Pacific/Niue" }, + { "NZ", "Pacific/Auckland" }, + { "NZ", "Pacific/Chatham" }, + { "OM", "Asia/Muscat" }, + { "PA", "America/Panama" }, + { "PE", "America/Lima" }, + { "PF", "Pacific/Tahiti" }, + { "PF", "Pacific/Marquesas" }, + { "PF", "Pacific/Gambier" }, + { "PG", "Pacific/Port_Moresby" }, + { "PH", "Asia/Manila" }, + { "PK", "Asia/Karachi" }, + { "PL", "Europe/Warsaw" }, + { "PM", "America/Miquelon" }, + { "PN", "Pacific/Pitcairn" }, + { "PR", "America/Puerto_Rico" }, + { "PS", "Asia/Gaza" }, + { "PS", "Asia/Hebron" }, + { "PT", "Europe/Lisbon" }, + { "PT", "Atlantic/Madeira" }, + { "PT", "Atlantic/Azores" }, + { "PW", "Pacific/Palau" }, + { "PY", "America/Asuncion" }, + { "QA", "Asia/Qatar" }, + { "RE", "Indian/Reunion" }, + { "RO", "Europe/Bucharest" }, + { "RS", "Europe/Belgrade" }, + { "RU", "Europe/Kaliningrad" }, + { "RU", "Europe/Moscow" }, + { "RU", "Europe/Volgograd" }, + { "RU", "Europe/Samara" }, + { "RU", "Asia/Yekaterinburg" }, + { "RU", "Asia/Omsk" }, + { "RU", "Asia/Novosibirsk" }, + { "RU", "Asia/Novokuznetsk" }, + { "RU", "Asia/Krasnoyarsk" }, + { "RU", "Asia/Irkutsk" }, + { "RU", "Asia/Yakutsk" }, + { "RU", "Asia/Khandyga" }, + { "RU", "Asia/Vladivostok" }, + { "RU", "Asia/Sakhalin" }, + { "RU", "Asia/Ust-Nera" }, + { "RU", "Asia/Magadan" }, + { "RU", "Asia/Kamchatka" }, + { "RU", "Asia/Anadyr" }, + { "RW", "Africa/Kigali" }, + { "SA", "Asia/Riyadh" }, + { "SB", "Pacific/Guadalcanal" }, + { "SC", "Indian/Mahe" }, + { "SD", "Africa/Khartoum" }, + { "SE", "Europe/Stockholm" }, + { "SG", "Asia/Singapore" }, + { "SH", "Atlantic/St_Helena" }, + { "SI", "Europe/Ljubljana" }, + { "SJ", "Arctic/Longyearbyen" }, + { "SK", "Europe/Bratislava" }, + { "SL", "Africa/Freetown" }, + { "SM", "Europe/San_Marino" }, + { "SN", "Africa/Dakar" }, + { "SO", "Africa/Mogadishu" }, + { "SR", "America/Paramaribo" }, + { "SS", "Africa/Juba" }, + { "ST", "Africa/Sao_Tome" }, + { "SV", "America/El_Salvador" }, + { "SX", "America/Lower_Princes" }, + { "SY", "Asia/Damascus" }, + { "SZ", "Africa/Mbabane" }, + { "TC", "America/Grand_Turk" }, + { "TD", "Africa/Ndjamena" }, + { "TF", "Indian/Kerguelen" }, + { "TG", "Africa/Lome" }, + { "TH", "Asia/Bangkok" }, + { "TJ", "Asia/Dushanbe" }, + { "TK", "Pacific/Fakaofo" }, + { "TL", "Asia/Dili" }, + { "TM", "Asia/Ashgabat" }, + { "TN", "Africa/Tunis" }, + { "TO", "Pacific/Tongatapu" }, + { "TR", "Europe/Istanbul" }, + { "TT", "America/Port_of_Spain" }, + { "TV", "Pacific/Funafuti" }, + { "TW", "Asia/Taipei" }, + { "TZ", "Africa/Dar_es_Salaam" }, + { "UA", "Europe/Kiev" }, + { "UA", "Europe/Uzhgorod" }, + { "UA", "Europe/Zaporozhye" }, + { "UA", "Europe/Simferopol" }, + { "UG", "Africa/Kampala" }, + { "UM", "Pacific/Johnston" }, + { "UM", "Pacific/Midway" }, + { "UM", "Pacific/Wake" }, + { "US", "America/New_York" }, + { "US", "America/Detroit" }, + { "US", "America/Kentucky/Louisville" }, + { "US", "America/Kentucky/Monticello" }, + { "US", "America/Indiana/Indianapolis" }, + { "US", "America/Indiana/Vincennes" }, + { "US", "America/Indiana/Winamac" }, + { "US", "America/Indiana/Marengo" }, + { "US", "America/Indiana/Petersburg" }, + { "US", "America/Indiana/Vevay" }, + { "US", "America/Chicago" }, + { "US", "America/Indiana/Tell_City" }, + { "US", "America/Indiana/Knox" }, + { "US", "America/Menominee" }, + { "US", "America/North_Dakota/Center" }, + { "US", "America/North_Dakota/New_Salem" }, + { "US", "America/North_Dakota/Beulah" }, + { "US", "America/Denver" }, + { "US", "America/Boise" }, + { "US", "America/Phoenix" }, + { "US", "America/Los_Angeles" }, + { "US", "America/Anchorage" }, + { "US", "America/Juneau" }, + { "US", "America/Sitka" }, + { "US", "America/Yakutat" }, + { "US", "America/Nome" }, + { "US", "America/Adak" }, + { "US", "America/Metlakatla" }, + { "US", "Pacific/Honolulu" }, + { "UY", "America/Montevideo" }, + { "UZ", "Asia/Samarkand" }, + { "UZ", "Asia/Tashkent" }, + { "VA", "Europe/Vatican" }, + { "VC", "America/St_Vincent" }, + { "VE", "America/Caracas" }, + { "VG", "America/Tortola" }, + { "VI", "America/St_Thomas" }, + { "VN", "Asia/Ho_Chi_Minh" }, + { "VU", "Pacific/Efate" }, + { "WF", "Pacific/Wallis" }, + { "WS", "Pacific/Apia" }, + { "YE", "Asia/Aden" }, + { "YT", "Indian/Mayotte" }, + { "ZA", "Africa/Johannesburg" }, + { "ZM", "Africa/Lusaka" }, + { "ZW", "Africa/Harare" }, + // The mappings below are custom additions to zone.tab. + { "GB", "Etc/GMT" }, + { "GB", "Etc/UTC" }, + { "GB", "Etc/UCT" }, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(olson_code_data); ++i) { + map_[olson_code_data[i].olson_code] = olson_code_data[i].country_code; + } + + // These are mapping from old codenames to new codenames. They are also + // part of public domain, and available at + // <http://www.ietf.org/timezones/data/backward>. + struct LinkData { + std::string old_code; + std::string new_code; + } link_data[] = { + { "Africa/Asmera", "Africa/Asmara" }, + { "Africa/Timbuktu", "Africa/Bamako" }, + { "America/Argentina/ComodRivadavia", "America/Argentina/Catamarca" }, + { "America/Atka", "America/Adak" }, + { "America/Buenos_Aires", "America/Argentina/Buenos_Aires" }, + { "America/Catamarca", "America/Argentina/Catamarca" }, + { "America/Coral_Harbour", "America/Atikokan" }, + { "America/Cordoba", "America/Argentina/Cordoba" }, + { "America/Ensenada", "America/Tijuana" }, + { "America/Fort_Wayne", "America/Indiana/Indianapolis" }, + { "America/Indianapolis", "America/Indiana/Indianapolis" }, + { "America/Jujuy", "America/Argentina/Jujuy" }, + { "America/Knox_IN", "America/Indiana/Knox" }, + { "America/Louisville", "America/Kentucky/Louisville" }, + { "America/Mendoza", "America/Argentina/Mendoza" }, + { "America/Porto_Acre", "America/Rio_Branco" }, + { "America/Rosario", "America/Argentina/Cordoba" }, + { "America/Virgin", "America/St_Thomas" }, + { "Asia/Ashkhabad", "Asia/Ashgabat" }, + { "Asia/Chungking", "Asia/Chongqing" }, + { "Asia/Dacca", "Asia/Dhaka" }, + { "Asia/Katmandu", "Asia/Kathmandu" }, + { "Asia/Calcutta", "Asia/Kolkata" }, + { "Asia/Macao", "Asia/Macau" }, + { "Asia/Tel_Aviv", "Asia/Jerusalem" }, + { "Asia/Saigon", "Asia/Ho_Chi_Minh" }, + { "Asia/Thimbu", "Asia/Thimphu" }, + { "Asia/Ujung_Pandang", "Asia/Makassar" }, + { "Asia/Ulan_Bator", "Asia/Ulaanbaatar" }, + { "Atlantic/Faeroe", "Atlantic/Faroe" }, + { "Atlantic/Jan_Mayen", "Europe/Oslo" }, + { "Australia/ACT", "Australia/Sydney" }, + { "Australia/Canberra", "Australia/Sydney" }, + { "Australia/LHI", "Australia/Lord_Howe" }, + { "Australia/NSW", "Australia/Sydney" }, + { "Australia/North", "Australia/Darwin" }, + { "Australia/Queensland", "Australia/Brisbane" }, + { "Australia/South", "Australia/Adelaide" }, + { "Australia/Tasmania", "Australia/Hobart" }, + { "Australia/Victoria", "Australia/Melbourne" }, + { "Australia/West", "Australia/Perth" }, + { "Australia/Yancowinna", "Australia/Broken_Hill" }, + { "Brazil/Acre", "America/Rio_Branco" }, + { "Brazil/DeNoronha", "America/Noronha" }, + { "Brazil/East", "America/Sao_Paulo" }, + { "Brazil/West", "America/Manaus" }, + { "Canada/Atlantic", "America/Halifax" }, + { "Canada/Central", "America/Winnipeg" }, + { "Canada/East-Saskatchewan", "America/Regina" }, + { "Canada/Eastern", "America/Toronto" }, + { "Canada/Mountain", "America/Edmonton" }, + { "Canada/Newfoundland", "America/St_Johns" }, + { "Canada/Pacific", "America/Vancouver" }, + { "Canada/Saskatchewan", "America/Regina" }, + { "Canada/Yukon", "America/Whitehorse" }, + { "Chile/Continental", "America/Santiago" }, + { "Chile/EasterIsland", "Pacific/Easter" }, + { "Cuba", "America/Havana" }, + { "Egypt", "Africa/Cairo" }, + { "Eire", "Europe/Dublin" }, + { "Europe/Belfast", "Europe/London" }, + { "Europe/Tiraspol", "Europe/Chisinau" }, + { "GB", "Europe/London" }, + { "GB-Eire", "Europe/London" }, + { "GMT+0", "Etc/GMT" }, + { "GMT-0", "Etc/GMT" }, + { "GMT0", "Etc/GMT" }, + { "Greenwich", "Etc/GMT" }, + { "Hongkong", "Asia/Hong_Kong" }, + { "Iceland", "Atlantic/Reykjavik" }, + { "Iran", "Asia/Tehran" }, + { "Israel", "Asia/Jerusalem" }, + { "Jamaica", "America/Jamaica" }, + { "Japan", "Asia/Tokyo" }, + { "Kwajalein", "Pacific/Kwajalein" }, + { "Libya", "Africa/Tripoli" }, + { "Mexico/BajaNorte", "America/Tijuana" }, + { "Mexico/BajaSur", "America/Mazatlan" }, + { "Mexico/General", "America/Mexico_City" }, + { "NZ", "Pacific/Auckland" }, + { "NZ-CHAT", "Pacific/Chatham" }, + { "Navajo", "America/Denver" }, + { "PRC", "Asia/Shanghai" }, + { "Pacific/Samoa", "Pacific/Pago_Pago" }, + { "Pacific/Yap", "Pacific/Chuuk" }, + { "Pacific/Truk", "Pacific/Chuuk" }, + { "Pacific/Ponape", "Pacific/Pohnpei" }, + { "Poland", "Europe/Warsaw" }, + { "Portugal", "Europe/Lisbon" }, + { "ROC", "Asia/Taipei" }, + { "ROK", "Asia/Seoul" }, + { "Singapore", "Asia/Singapore" }, + { "Turkey", "Europe/Istanbul" }, + { "UCT", "Etc/UCT" }, + { "US/Alaska", "America/Anchorage" }, + { "US/Aleutian", "America/Adak" }, + { "US/Arizona", "America/Phoenix" }, + { "US/Central", "America/Chicago" }, + { "US/East-Indiana", "America/Indiana/Indianapolis" }, + { "US/Eastern", "America/New_York" }, + { "US/Hawaii", "Pacific/Honolulu" }, + { "US/Indiana-Starke", "America/Indiana/Knox" }, + { "US/Michigan", "America/Detroit" }, + { "US/Mountain", "America/Denver" }, + { "US/Pacific", "America/Los_Angeles" }, + { "US/Samoa", "Pacific/Pago_Pago" }, + { "UTC", "Etc/UTC" }, + { "Universal", "Etc/UTC" }, + { "W-SU", "Europe/Moscow" }, + { "Zulu", "Etc/UTC" }, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(link_data); ++i) { + map_[link_data[i].old_code] = map_[link_data[i].new_code]; + } + } + + friend struct DefaultSingletonTraits<TimezoneMap>; + + std::map<std::string, std::string> map_; + + DISALLOW_COPY_AND_ASSIGN(TimezoneMap); +}; + +} // namespace + +std::string CountryCodeForCurrentTimezone() { + scoped_ptr<icu::TimeZone> zone(icu::TimeZone::createDefault()); + icu::UnicodeString id; + zone->getID(id); + string16 olson_code(id.getBuffer(), id.length()); + return TimezoneMap::GetInstance()->CountryCodeForTimezone( + UTF16ToUTF8(olson_code)); +} + +} // namespace base diff --git a/chromium/base/i18n/timezone.h b/chromium/base/i18n/timezone.h new file mode 100644 index 00000000000..b7275f7b00b --- /dev/null +++ b/chromium/base/i18n/timezone.h @@ -0,0 +1,21 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_I18N_TIMEZONE_H_ +#define BASE_I18N_TIMEZONE_H_ + +#include <string> + +#include "base/i18n/base_i18n_export.h" + +namespace base { + +// Checks the system timezone and turns it into a two-character ASCII country +// code. This may fail (for example, it will always fail on Android), in which +// case it will return an empty string. +BASE_I18N_EXPORT std::string CountryCodeForCurrentTimezone(); + +} // namespace base + +#endif // BASE_TIME_TIMEZONE_H_ diff --git a/chromium/base/i18n/timezone_unittest.cc b/chromium/base/i18n/timezone_unittest.cc new file mode 100644 index 00000000000..2cdcc422985 --- /dev/null +++ b/chromium/base/i18n/timezone_unittest.cc @@ -0,0 +1,21 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/i18n/timezone.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace { + +TEST(TimezoneTest, CountryCodeForCurrentTimezone) { + std::string country_code = CountryCodeForCurrentTimezone(); + // On some systems (such as Android or some flavors of Linux), icu may come up + // empty. + if (!country_code.empty()) + EXPECT_EQ(2U, country_code.size()); +} + +} // namespace +} // namespace base diff --git a/chromium/base/ios/device_util.h b/chromium/base/ios/device_util.h index fe4833dd362..a4fa4887d35 100644 --- a/chromium/base/ios/device_util.h +++ b/chromium/base/ios/device_util.h @@ -40,7 +40,7 @@ namespace device_util { // x86_64 -> Simulator std::string GetPlatform(); -// Returns true if the application is running on a high-ram device. (>=250M). +// Returns true if the application is running on a high-ram device. (>=500M). bool IsRunningOnHighRamDevice(); // Returns true if the device has only one core. diff --git a/chromium/base/ios/device_util.mm b/chromium/base/ios/device_util.mm index 96008332bef..0d516f9a2b4 100644 --- a/chromium/base/ios/device_util.mm +++ b/chromium/base/ios/device_util.mm @@ -72,8 +72,8 @@ bool IsRunningOnHighRamDevice() { uint64_t memory_size = 0; size_t size = sizeof(memory_size); if (sysctlbyname("hw.memsize", &memory_size, &size, NULL, 0) == 0) { - // Anything >= 250M, call high ram. - return memory_size >= 250 * 1024 * 1024; + // Anything >= 500M, call high ram. + return memory_size >= 500 * 1024 * 1024; } return false; } diff --git a/chromium/base/ios/scoped_critical_action.h b/chromium/base/ios/scoped_critical_action.h index 660a83ad024..803d5875176 100644 --- a/chromium/base/ios/scoped_critical_action.h +++ b/chromium/base/ios/scoped_critical_action.h @@ -5,6 +5,7 @@ #ifndef BASE_IOS_SCOPED_CRITICAL_ACTION_H_ #define BASE_IOS_SCOPED_CRITICAL_ACTION_H_ +#include "base/memory/ref_counted.h" #include "base/synchronization/lock.h" namespace base { @@ -29,15 +30,34 @@ class ScopedCriticalAction { ~ScopedCriticalAction(); private: - // Informs the OS that the background task has completed. - void EndBackgroundTask(); - - // |UIBackgroundTaskIdentifier| returned by - // |beginBackgroundTaskWithExpirationHandler:| when marking the beginning of - // a long-running background task. It is defined as an |unsigned int| instead - // of a |UIBackgroundTaskIdentifier| so this class can be used in .cc files. - unsigned int background_task_id_; - Lock background_task_id_lock_; + // Core logic; ScopedCriticalAction should not be reference counted so + // that it follows the normal pattern of stack-allocating ScopedFoo objects, + // but the expiration handler needs to have a reference counted object to + // refer to. + class Core : public base::RefCountedThreadSafe<Core> { + public: + Core(); + + // Informs the OS that the background task has completed. + void EndBackgroundTask(); + + private: + friend base::RefCountedThreadSafe<Core>; + ~Core(); + + // |UIBackgroundTaskIdentifier| returned by + // |beginBackgroundTaskWithExpirationHandler:| when marking the beginning of + // a long-running background task. It is defined as an |unsigned int| + // instead of a |UIBackgroundTaskIdentifier| so this class can be used in + // .cc files. + unsigned int background_task_id_; + Lock background_task_id_lock_; + + DISALLOW_COPY_AND_ASSIGN(Core); + }; + + // The instance of the core that drives the background task. + scoped_refptr<Core> core_; DISALLOW_COPY_AND_ASSIGN(ScopedCriticalAction); }; diff --git a/chromium/base/ios/scoped_critical_action.mm b/chromium/base/ios/scoped_critical_action.mm index 734c0a215b6..9dad70ed6ba 100644 --- a/chromium/base/ios/scoped_critical_action.mm +++ b/chromium/base/ios/scoped_critical_action.mm @@ -7,22 +7,32 @@ #import <UIKit/UIKit.h> #include "base/logging.h" +#include "base/memory/ref_counted.h" #include "base/synchronization/lock.h" namespace base { namespace ios { +ScopedCriticalAction::ScopedCriticalAction() + : core_(new ScopedCriticalAction::Core()) { +} + +ScopedCriticalAction::~ScopedCriticalAction() { + core_->EndBackgroundTask(); +} + // This implementation calls |beginBackgroundTaskWithExpirationHandler:| when // instantiated and |endBackgroundTask:| when destroyed, creating a scope whose // execution will continue (temporarily) even after the app is backgrounded. -ScopedCriticalAction::ScopedCriticalAction() { +ScopedCriticalAction::Core::Core() { + scoped_refptr<ScopedCriticalAction::Core> core = this; background_task_id_ = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ DLOG(WARNING) << "Background task with id " << background_task_id_ << " expired."; // Note if |endBackgroundTask:| is not called for each task before time // expires, the system kills the application. - EndBackgroundTask(); + core->EndBackgroundTask(); }]; if (background_task_id_ == UIBackgroundTaskInvalid) { DLOG(WARNING) << @@ -32,11 +42,11 @@ ScopedCriticalAction::ScopedCriticalAction() { } } -ScopedCriticalAction::~ScopedCriticalAction() { - EndBackgroundTask(); +ScopedCriticalAction::Core::~Core() { + DCHECK_EQ(background_task_id_, UIBackgroundTaskInvalid); } -void ScopedCriticalAction::EndBackgroundTask() { +void ScopedCriticalAction::Core::EndBackgroundTask() { UIBackgroundTaskIdentifier task_id; { AutoLock lock_scope(background_task_id_lock_); diff --git a/chromium/base/json/json_reader.cc b/chromium/base/json/json_reader.cc index 593273ebd44..cbf6c99eda5 100644 --- a/chromium/base/json/json_reader.cc +++ b/chromium/base/json/json_reader.cc @@ -9,6 +9,10 @@ namespace base { +// Values 1000 and above are used by JSONFileValueSerializer::JsonFileError. +COMPILE_ASSERT(JSONReader::JSON_PARSE_ERROR_COUNT < 1000, + json_reader_error_out_of_bounds); + const char* JSONReader::kInvalidEscape = "Invalid escape sequence."; const char* JSONReader::kSyntaxError = diff --git a/chromium/base/json/json_reader.h b/chromium/base/json/json_reader.h index e6028462788..9b306878e7f 100644 --- a/chromium/base/json/json_reader.h +++ b/chromium/base/json/json_reader.h @@ -73,6 +73,7 @@ class BASE_EXPORT JSONReader { JSON_UNEXPECTED_DATA_AFTER_ROOT, JSON_UNSUPPORTED_ENCODING, JSON_UNQUOTED_DICTIONARY_KEY, + JSON_PARSE_ERROR_COUNT }; // String versions of parse error codes. diff --git a/chromium/base/json/json_string_value_serializer.h b/chromium/base/json/json_string_value_serializer.h index 8aa3f95bee5..014ef9f7c57 100644 --- a/chromium/base/json/json_string_value_serializer.h +++ b/chromium/base/json/json_string_value_serializer.h @@ -47,7 +47,7 @@ class BASE_EXPORT JSONStringValueSerializer : public base::ValueSerializer { // Attempt to deserialize the data structure encoded in the string passed // in to the constructor into a structure of Value objects. If the return // value is NULL, and if |error_code| is non-null, |error_code| will - // contain an integer error code (either JsonFileError or JsonParseError). + // contain an integer error code (a JsonParseError in this case). // If |error_message| is non-null, it will be filled in with a formatted // error message including the location of the error if appropriate. // The caller takes ownership of the returned value. diff --git a/chromium/base/json/json_value_serializer_unittest.cc b/chromium/base/json/json_value_serializer_unittest.cc index 314cd07a5b8..44c0a57cd27 100644 --- a/chromium/base/json/json_value_serializer_unittest.cc +++ b/chromium/base/json/json_value_serializer_unittest.cc @@ -235,22 +235,23 @@ TEST(JSONValueSerializerTest, StringEscape) { std::string all_chars_expected = "\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000B\\f\\r" "\\u000E\\u000F\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017" - "\\u0018\\u0019\\u001A\\u001B\\u001C\\u001D\\u001E\\u001F !\\\"" - "#$%&'()*+,-./0123456789:;\\u003C=\\u003E?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\" - "\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\\u007F\\u0080\\u0081\\u0082\\u0083" - "\\u0084\\u0085\\u0086\\u0087\\u0088\\u0089\\u008A\\u008B\\u008C\\u008D" - "\\u008E\\u008F\\u0090\\u0091\\u0092\\u0093\\u0094\\u0095\\u0096\\u0097" - "\\u0098\\u0099\\u009A\\u009B\\u009C\\u009D\\u009E\\u009F\\u00A0\\u00A1" - "\\u00A2\\u00A3\\u00A4\\u00A5\\u00A6\\u00A7\\u00A8\\u00A9\\u00AA\\u00AB" - "\\u00AC\\u00AD\\u00AE\\u00AF\\u00B0\\u00B1\\u00B2\\u00B3\\u00B4\\u00B5" - "\\u00B6\\u00B7\\u00B8\\u00B9\\u00BA\\u00BB\\u00BC\\u00BD\\u00BE\\u00BF" - "\\u00C0\\u00C1\\u00C2\\u00C3\\u00C4\\u00C5\\u00C6\\u00C7\\u00C8\\u00C9" - "\\u00CA\\u00CB\\u00CC\\u00CD\\u00CE\\u00CF\\u00D0\\u00D1\\u00D2\\u00D3" - "\\u00D4\\u00D5\\u00D6\\u00D7\\u00D8\\u00D9\\u00DA\\u00DB\\u00DC\\u00DD" - "\\u00DE\\u00DF\\u00E0\\u00E1\\u00E2\\u00E3\\u00E4\\u00E5\\u00E6\\u00E7" - "\\u00E8\\u00E9\\u00EA\\u00EB\\u00EC\\u00ED\\u00EE\\u00EF\\u00F0\\u00F1" - "\\u00F2\\u00F3\\u00F4\\u00F5\\u00F6\\u00F7\\u00F8\\u00F9\\u00FA\\u00FB" - "\\u00FC\\u00FD\\u00FE\\u00FF"; + "\\u0018\\u0019\\u001A\\u001B\\u001C\\u001D\\u001E\\u001F !\\\"#$%&'()*+," + "-./0123456789:;\\u003C=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcde" + "fghijklmnopqrstuvwxyz{|}~\x7F\xC2\x80\xC2\x81\xC2\x82\xC2\x83\xC2\x84" + "\xC2\x85\xC2\x86\xC2\x87\xC2\x88\xC2\x89\xC2\x8A\xC2\x8B\xC2\x8C\xC2\x8D" + "\xC2\x8E\xC2\x8F\xC2\x90\xC2\x91\xC2\x92\xC2\x93\xC2\x94\xC2\x95\xC2\x96" + "\xC2\x97\xC2\x98\xC2\x99\xC2\x9A\xC2\x9B\xC2\x9C\xC2\x9D\xC2\x9E\xC2\x9F" + "\xC2\xA0\xC2\xA1\xC2\xA2\xC2\xA3\xC2\xA4\xC2\xA5\xC2\xA6\xC2\xA7\xC2\xA8" + "\xC2\xA9\xC2\xAA\xC2\xAB\xC2\xAC\xC2\xAD\xC2\xAE\xC2\xAF\xC2\xB0\xC2\xB1" + "\xC2\xB2\xC2\xB3\xC2\xB4\xC2\xB5\xC2\xB6\xC2\xB7\xC2\xB8\xC2\xB9\xC2\xBA" + "\xC2\xBB\xC2\xBC\xC2\xBD\xC2\xBE\xC2\xBF\xC3\x80\xC3\x81\xC3\x82\xC3\x83" + "\xC3\x84\xC3\x85\xC3\x86\xC3\x87\xC3\x88\xC3\x89\xC3\x8A\xC3\x8B\xC3\x8C" + "\xC3\x8D\xC3\x8E\xC3\x8F\xC3\x90\xC3\x91\xC3\x92\xC3\x93\xC3\x94\xC3\x95" + "\xC3\x96\xC3\x97\xC3\x98\xC3\x99\xC3\x9A\xC3\x9B\xC3\x9C\xC3\x9D\xC3\x9E" + "\xC3\x9F\xC3\xA0\xC3\xA1\xC3\xA2\xC3\xA3\xC3\xA4\xC3\xA5\xC3\xA6\xC3\xA7" + "\xC3\xA8\xC3\xA9\xC3\xAA\xC3\xAB\xC3\xAC\xC3\xAD\xC3\xAE\xC3\xAF\xC3\xB0" + "\xC3\xB1\xC3\xB2\xC3\xB3\xC3\xB4\xC3\xB5\xC3\xB6\xC3\xB7\xC3\xB8\xC3\xB9" + "\xC3\xBA\xC3\xBB\xC3\xBC\xC3\xBD\xC3\xBE\xC3\xBF"; std::string expected_output = "{\"all_chars\":\"" + all_chars_expected + "\"}"; @@ -273,7 +274,7 @@ TEST(JSONValueSerializerTest, UnicodeStrings) { string16 test(WideToUTF16(L"\x7F51\x9875")); root.SetString("web", test); - std::string expected = "{\"web\":\"\\u7F51\\u9875\"}"; + std::string expected = "{\"web\":\"\xE7\xBD\x91\xE9\xA1\xB5\"}"; std::string actual; JSONStringValueSerializer serializer(&actual); diff --git a/chromium/base/json/json_writer.cc b/chromium/base/json/json_writer.cc index 6a9cc6aa470..d6006638219 100644 --- a/chromium/base/json/json_writer.cc +++ b/chromium/base/json/json_writer.cc @@ -21,28 +21,24 @@ static const char kPrettyPrintLineEnding[] = "\r\n"; static const char kPrettyPrintLineEnding[] = "\n"; #endif -/* static */ -const char* JSONWriter::kEmptyArray = "[]"; - -/* static */ +// static void JSONWriter::Write(const Value* const node, std::string* json) { WriteWithOptions(node, 0, json); } -/* static */ +// static void JSONWriter::WriteWithOptions(const Value* const node, int options, std::string* json) { json->clear(); // Is there a better way to estimate the size of the output? json->reserve(1024); - bool escape = !(options & OPTIONS_DO_NOT_ESCAPE); bool omit_binary_values = !!(options & OPTIONS_OMIT_BINARY_VALUES); bool omit_double_type_preservation = !!(options & OPTIONS_OMIT_DOUBLE_TYPE_PRESERVATION); bool pretty_print = !!(options & OPTIONS_PRETTY_PRINT); - JSONWriter writer(escape, omit_binary_values, omit_double_type_preservation, + JSONWriter writer(omit_binary_values, omit_double_type_preservation, pretty_print, json); writer.BuildJSONString(node, 0); @@ -50,11 +46,10 @@ void JSONWriter::WriteWithOptions(const Value* const node, int options, json->append(kPrettyPrintLineEnding); } -JSONWriter::JSONWriter(bool escape, bool omit_binary_values, +JSONWriter::JSONWriter(bool omit_binary_values, bool omit_double_type_preservation, bool pretty_print, std::string* json) - : escape_(escape), - omit_binary_values_(omit_binary_values), + : omit_binary_values_(omit_binary_values), omit_double_type_preservation_(omit_double_type_preservation), pretty_print_(pretty_print), json_string_(json) { @@ -123,11 +118,7 @@ void JSONWriter::BuildJSONString(const Value* const node, int depth) { std::string value; bool result = node->GetAsString(&value); DCHECK(result); - if (escape_) { - JsonDoubleQuote(UTF8ToUTF16(value), true, json_string_); - } else { - JsonDoubleQuote(value, true, json_string_); - } + EscapeJSONString(value, true, json_string_); break; } @@ -169,7 +160,7 @@ void JSONWriter::BuildJSONString(const Value* const node, int depth) { json_string_->append(kPrettyPrintLineEnding); const DictionaryValue* dict = - static_cast<const DictionaryValue*>(node); + static_cast<const DictionaryValue*>(node); bool first_entry = true; for (DictionaryValue::Iterator itr(*dict); !itr.IsAtEnd(); itr.Advance(), first_entry = false) { @@ -186,7 +177,8 @@ void JSONWriter::BuildJSONString(const Value* const node, int depth) { if (pretty_print_) IndentLine(depth + 1); - AppendQuotedString(itr.key()); + + EscapeJSONString(itr.key(), true, json_string_); if (pretty_print_) { json_string_->append(": "); } else { @@ -218,12 +210,6 @@ void JSONWriter::BuildJSONString(const Value* const node, int depth) { } } -void JSONWriter::AppendQuotedString(const std::string& str) { - // TODO(viettrungluu): |str| is UTF-8, not ASCII, so to properly escape it we - // have to convert it to UTF-16. This round-trip is suboptimal. - JsonDoubleQuote(UTF8ToUTF16(str), true, json_string_); -} - void JSONWriter::IndentLine(int depth) { // It may be faster to keep an indent string so we don't have to keep // reallocating. diff --git a/chromium/base/json/json_writer.h b/chromium/base/json/json_writer.h index 94052c80343..e4a143c7780 100644 --- a/chromium/base/json/json_writer.h +++ b/chromium/base/json/json_writer.h @@ -17,24 +17,19 @@ class Value; class BASE_EXPORT JSONWriter { public: enum Options { - // Do not escape the string, preserving its UTF8 characters. It is useful - // if you can pass the resulting string to the JSON parser in binary form - // (as UTF8). - OPTIONS_DO_NOT_ESCAPE = 1 << 0, - // For values of binary type, the value (and key if within a dictionary) // will be omitted from the output. - OPTIONS_OMIT_BINARY_VALUES = 1 << 1, + OPTIONS_OMIT_BINARY_VALUES = 1 << 0, // This option instructs the writer to write doubles that have no fractional // part as a normal integer (i.e., without using exponential notation // or appending a '.0') as long as the value is within the range of a // 64-bit int. - OPTIONS_OMIT_DOUBLE_TYPE_PRESERVATION = 1 << 2, + OPTIONS_OMIT_DOUBLE_TYPE_PRESERVATION = 1 << 1, // Return a slightly nicer formatted json string (pads with whitespace to // help with readability). - OPTIONS_PRETTY_PRINT = 1 << 3 + OPTIONS_PRETTY_PRINT = 1 << 2, }; // Given a root node, generates a JSON string and puts it into |json|. @@ -48,12 +43,8 @@ class BASE_EXPORT JSONWriter { static void WriteWithOptions(const Value* const node, int options, std::string* json); - // A static, constant JSON string representing an empty array. Useful - // for empty JSON argument passing. - static const char* kEmptyArray; - private: - JSONWriter(bool escape, bool omit_binary_values, + JSONWriter(bool omit_binary_values, bool omit_double_type_preservation, bool pretty_print, std::string* json); @@ -61,13 +52,9 @@ class BASE_EXPORT JSONWriter { // json_string_ will contain the JSON. void BuildJSONString(const Value* const node, int depth); - // Appends a quoted, escaped, version of (UTF-8) str to json_string_. - void AppendQuotedString(const std::string& str); - // Adds space to json_string_ for the indent level. void IndentLine(int depth); - bool escape_; bool omit_binary_values_; bool omit_double_type_preservation_; bool pretty_print_; diff --git a/chromium/base/json/string_escape.cc b/chromium/base/json/string_escape.cc index 10ea6707465..a3b0735191e 100644 --- a/chromium/base/json/string_escape.cc +++ b/chromium/base/json/string_escape.cc @@ -8,40 +8,56 @@ #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversion_utils.h" +#include "base/strings/utf_string_conversions.h" +#include "base/third_party/icu/icu_utf.h" namespace base { namespace { -// Try to escape |c| as a "SingleEscapeCharacter" (\n, etc). If successful, -// returns true and appends the escape sequence to |dst|. This isn't required -// by the spec, but it's more readable by humans than the \uXXXX alternatives. -template<typename CHAR> -static bool JsonSingleEscapeChar(const CHAR c, std::string* dst) { +// Format string for printing a \uXXXX escape sequence. +const char kU16EscapeFormat[] = "\\u%04X"; + +// The code point to output for an invalid input code unit. +const uint32 kReplacementCodePoint = 0xFFFD; + +// Used below in EscapeSpecialCodePoint(). +COMPILE_ASSERT('<' == 0x3C, less_than_sign_is_0x3c); + +// Try to escape the |code_point| if it is a known special character. If +// successful, returns true and appends the escape sequence to |dest|. This +// isn't required by the spec, but it's more readable by humans. +bool EscapeSpecialCodePoint(uint32 code_point, std::string* dest) { // WARNING: if you add a new case here, you need to update the reader as well. // Note: \v is in the reader, but not here since the JSON spec doesn't // allow it. - switch (c) { + switch (code_point) { case '\b': - dst->append("\\b"); + dest->append("\\b"); break; case '\f': - dst->append("\\f"); + dest->append("\\f"); break; case '\n': - dst->append("\\n"); + dest->append("\\n"); break; case '\r': - dst->append("\\r"); + dest->append("\\r"); break; case '\t': - dst->append("\\t"); + dest->append("\\t"); break; case '\\': - dst->append("\\\\"); + dest->append("\\\\"); break; case '"': - dst->append("\\\""); + dest->append("\\\""); + break; + // Escape < to prevent script execution; escaping > is not necessary and + // not doing so save a few bytes. + case '<': + dest->append("\\u003C"); break; default: return false; @@ -49,57 +65,90 @@ static bool JsonSingleEscapeChar(const CHAR c, std::string* dst) { return true; } -template <class STR> -void JsonDoubleQuoteT(const STR& str, - bool put_in_quotes, - std::string* dst) { +template <typename S> +bool EscapeJSONStringImpl(const S& str, bool put_in_quotes, std::string* dest) { + bool did_replacement = false; + if (put_in_quotes) - dst->push_back('"'); - - for (typename STR::const_iterator it = str.begin(); it != str.end(); ++it) { - typename ToUnsigned<typename STR::value_type>::Unsigned c = *it; - if (!JsonSingleEscapeChar(c, dst)) { - if (c < 32 || c > 126 || c == '<' || c == '>') { - // 1. Escaping <, > to prevent script execution. - // 2. Technically, we could also pass through c > 126 as UTF8, but this - // is also optional. It would also be a pain to implement here. - unsigned int as_uint = static_cast<unsigned int>(c); - base::StringAppendF(dst, "\\u%04X", as_uint); - } else { - unsigned char ascii = static_cast<unsigned char>(*it); - dst->push_back(ascii); - } + dest->push_back('"'); + + // Casting is necessary because ICU uses int32. Try and do so safely. + CHECK_LE(str.length(), static_cast<size_t>(kint32max)); + const int32 length = static_cast<int32>(str.length()); + + for (int32 i = 0; i < length; ++i) { + uint32 code_point; + if (!ReadUnicodeCharacter(str.data(), length, &i, &code_point)) { + code_point = kReplacementCodePoint; + did_replacement = true; } + + if (EscapeSpecialCodePoint(code_point, dest)) + continue; + + // Escape non-printing characters. + if (code_point < 32) + base::StringAppendF(dest, kU16EscapeFormat, code_point); + else + WriteUnicodeCharacter(code_point, dest); } if (put_in_quotes) - dst->push_back('"'); + dest->push_back('"'); + + return !did_replacement; } } // namespace -void JsonDoubleQuote(const StringPiece& str, - bool put_in_quotes, - std::string* dst) { - JsonDoubleQuoteT(str, put_in_quotes, dst); +bool EscapeJSONString(const StringPiece& str, + bool put_in_quotes, + std::string* dest) { + return EscapeJSONStringImpl(str, put_in_quotes, dest); } -std::string GetDoubleQuotedJson(const StringPiece& str) { - std::string dst; - JsonDoubleQuote(str, true, &dst); - return dst; +bool EscapeJSONString(const StringPiece16& str, + bool put_in_quotes, + std::string* dest) { + return EscapeJSONStringImpl(str, put_in_quotes, dest); +} + +std::string GetQuotedJSONString(const StringPiece& str) { + std::string dest; + bool ok = EscapeJSONStringImpl(str, true, &dest); + DCHECK(ok); + return dest; } -void JsonDoubleQuote(const StringPiece16& str, - bool put_in_quotes, - std::string* dst) { - JsonDoubleQuoteT(str, put_in_quotes, dst); +std::string GetQuotedJSONString(const StringPiece16& str) { + std::string dest; + bool ok = EscapeJSONStringImpl(str, true, &dest); + DCHECK(ok); + return dest; } -std::string GetDoubleQuotedJson(const StringPiece16& str) { - std::string dst; - JsonDoubleQuote(str, true, &dst); - return dst; +std::string EscapeBytesAsInvalidJSONString(const StringPiece& str, + bool put_in_quotes) { + std::string dest; + + if (put_in_quotes) + dest.push_back('"'); + + for (StringPiece::const_iterator it = str.begin(); it != str.end(); ++it) { + ToUnsigned<StringPiece::value_type>::Unsigned c = *it; + if (EscapeSpecialCodePoint(c, &dest)) + continue; + + if (c < 32 || c > 126) + base::StringAppendF(&dest, kU16EscapeFormat, c); + else + dest.push_back(*it); + } + + if (put_in_quotes) + dest.push_back('"'); + + return dest; } } // namespace base diff --git a/chromium/base/json/string_escape.h b/chromium/base/json/string_escape.h index 0f16f59d4fd..b66b7e54532 100644 --- a/chromium/base/json/string_escape.h +++ b/chromium/base/json/string_escape.h @@ -1,8 +1,8 @@ // Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// -// This file defines utility functions for escaping strings. + +// This file defines utility functions for escaping strings suitable for JSON. #ifndef BASE_JSON_STRING_ESCAPE_H_ #define BASE_JSON_STRING_ESCAPE_H_ @@ -14,24 +14,46 @@ namespace base { -// Escape |str| appropriately for a JSON string literal, _appending_ the -// result to |dst|. This will create unicode escape sequences (\uXXXX). -// If |put_in_quotes| is true, the result will be surrounded in double quotes. -// The outputted literal, when interpreted by the browser, should result in a -// javascript string that is identical and the same length as the input |str|. -BASE_EXPORT void JsonDoubleQuote(const StringPiece& str, - bool put_in_quotes, - std::string* dst); - -// Same as above, but always returns the result double quoted. -BASE_EXPORT std::string GetDoubleQuotedJson(const StringPiece& str); - -BASE_EXPORT void JsonDoubleQuote(const StringPiece16& str, - bool put_in_quotes, - std::string* dst); - -// Same as above, but always returns the result double quoted. -BASE_EXPORT std::string GetDoubleQuotedJson(const StringPiece16& str); +// Appends to |dest| an escaped version of |str|. Valid UTF-8 code units will +// pass through from the input to the output. Invalid code units will be +// replaced with the U+FFFD replacement character. This function returns true +// if no replacement was necessary and false if there was a lossy replacement. +// On return, |dest| will contain a valid UTF-8 JSON string. +// +// Non-printing control characters will be escaped as \uXXXX sequences for +// readability. +// +// If |put_in_quotes| is true, then a leading and trailing double-quote mark +// will be appended to |dest| as well. +BASE_EXPORT bool EscapeJSONString(const StringPiece& str, + bool put_in_quotes, + std::string* dest); + +// Performs a similar function to the UTF-8 StringPiece version above, +// converting UTF-16 code units to UTF-8 code units and escaping non-printing +// control characters. On return, |dest| will contain a valid UTF-8 JSON string. +BASE_EXPORT bool EscapeJSONString(const StringPiece16& str, + bool put_in_quotes, + std::string* dest); + +// Helper functions that wrap the above two functions but return the value +// instead of appending. |put_in_quotes| is always true. +BASE_EXPORT std::string GetQuotedJSONString(const StringPiece& str); +BASE_EXPORT std::string GetQuotedJSONString(const StringPiece16& str); + +// Given an arbitrary byte string |str|, this will escape all non-ASCII bytes +// as \uXXXX escape sequences. This function is *NOT* meant to be used with +// Unicode strings and does not validate |str| as one. +// +// CAVEAT CALLER: The output of this function may not be valid JSON, since +// JSON requires escape sequences to be valid UTF-16 code units. This output +// will be mangled if passed to to the base::JSONReader, since the reader will +// interpret it as UTF-16 and convert it to UTF-8. +// +// The output of this function takes the *appearance* of JSON but is not in +// fact valid according to RFC 4627. +BASE_EXPORT std::string EscapeBytesAsInvalidJSONString(const StringPiece& str, + bool put_in_quotes); } // namespace base diff --git a/chromium/base/json/string_escape_unittest.cc b/chromium/base/json/string_escape_unittest.cc index f9219943744..7d82f9bd9b1 100644 --- a/chromium/base/json/string_escape_unittest.cc +++ b/chromium/base/json/string_escape_unittest.cc @@ -1,104 +1,182 @@ -// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Copyright (c) 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/json/string_escape.h" + +#include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "testing/gtest/include/gtest/gtest.h" namespace base { -namespace { - -const struct json_narrow_test_data { - const char* to_escape; - const char* escaped; -} json_narrow_cases[] = { - {"\b\001aZ\"\\wee", "\\b\\u0001aZ\\\"\\\\wee"}, - {"a\b\f\n\r\t\v\1\\.\"z", - "a\\b\\f\\n\\r\\t\\u000B\\u0001\\\\.\\\"z"}, - {"b\x0f\x7f\xf0\xff!", "b\\u000F\\u007F\\u00F0\\u00FF!"}, - {"c<>d", "c\\u003C\\u003Ed"}, -}; - -} // namespace - -TEST(StringEscapeTest, JsonDoubleQuoteNarrow) { - for (size_t i = 0; i < arraysize(json_narrow_cases); ++i) { - const char* in_ptr = json_narrow_cases[i].to_escape; +TEST(JSONStringEscapeTest, EscapeUTF8) { + const struct { + const char* to_escape; + const char* escaped; + } cases[] = { + {"\b\001aZ\"\\wee", "\\b\\u0001aZ\\\"\\\\wee"}, + {"a\b\f\n\r\t\v\1\\.\"z", + "a\\b\\f\\n\\r\\t\\u000B\\u0001\\\\.\\\"z"}, + {"b\x0f\x7f\xf0\xff!", // \xf0\xff is not a valid UTF-8 unit. + "b\\u000F\x7F\xEF\xBF\xBD\xEF\xBF\xBD!"}, + {"c<>d", "c\\u003C>d"}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + const char* in_ptr = cases[i].to_escape; std::string in_str = in_ptr; + std::string out; - JsonDoubleQuote(in_ptr, false, &out); - EXPECT_EQ(std::string(json_narrow_cases[i].escaped), out); + EscapeJSONString(in_ptr, false, &out); + EXPECT_EQ(std::string(cases[i].escaped), out); + EXPECT_TRUE(IsStringUTF8(out)); + out.erase(); - JsonDoubleQuote(in_str, false, &out); - EXPECT_EQ(std::string(json_narrow_cases[i].escaped), out); + bool convert_ok = EscapeJSONString(in_str, false, &out); + EXPECT_EQ(std::string(cases[i].escaped), out); + EXPECT_TRUE(IsStringUTF8(out)); + + if (convert_ok) { + std::string fooout = GetQuotedJSONString(in_str); + EXPECT_EQ("\"" + std::string(cases[i].escaped) + "\"", fooout); + EXPECT_TRUE(IsStringUTF8(out)); + } } - std::string in = json_narrow_cases[0].to_escape; + std::string in = cases[0].to_escape; std::string out; - JsonDoubleQuote(in, false, &out); + EscapeJSONString(in, false, &out); + EXPECT_TRUE(IsStringUTF8(out)); // test quoting std::string out_quoted; - JsonDoubleQuote(in, true, &out_quoted); + EscapeJSONString(in, true, &out_quoted); EXPECT_EQ(out.length() + 2, out_quoted.length()); EXPECT_EQ(out_quoted.find(out), 1U); + EXPECT_TRUE(IsStringUTF8(out_quoted)); // now try with a NULL in the string std::string null_prepend = "test"; null_prepend.push_back(0); in = null_prepend + in; std::string expected = "test\\u0000"; - expected += json_narrow_cases[0].escaped; + expected += cases[0].escaped; out.clear(); - JsonDoubleQuote(in, false, &out); + EscapeJSONString(in, false, &out); EXPECT_EQ(expected, out); + EXPECT_TRUE(IsStringUTF8(out)); } -namespace { - -const struct json_wide_test_data { - const wchar_t* to_escape; - const char* escaped; -} json_wide_cases[] = { - {L"b\uffb1\u00ff", "b\\uFFB1\\u00FF"}, - {L"\b\001aZ\"\\wee", "\\b\\u0001aZ\\\"\\\\wee"}, - {L"a\b\f\n\r\t\v\1\\.\"z", - "a\\b\\f\\n\\r\\t\\u000B\\u0001\\\\.\\\"z"}, - {L"b\x0f\x7f\xf0\xff!", "b\\u000F\\u007F\\u00F0\\u00FF!"}, - {L"c<>d", "c\\u003C\\u003Ed"}, -}; +TEST(JSONStringEscapeTest, EscapeUTF16) { + const struct { + const wchar_t* to_escape; + const char* escaped; + } cases[] = { + {L"b\uffb1\u00ff", "b\xEF\xBE\xB1\xC3\xBF"}, + {L"\b\001aZ\"\\wee", "\\b\\u0001aZ\\\"\\\\wee"}, + {L"a\b\f\n\r\t\v\1\\.\"z", + "a\\b\\f\\n\\r\\t\\u000B\\u0001\\\\.\\\"z"}, + {L"b\x0f\x7f\xf0\xff!", "b\\u000F\x7F\xC3\xB0\xC3\xBF!"}, + {L"c<>d", "c\\u003C>d"}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + string16 in = WideToUTF16(cases[i].to_escape); -} // namespace - -TEST(StringEscapeTest, JsonDoubleQuoteWide) { - for (size_t i = 0; i < arraysize(json_wide_cases); ++i) { std::string out; - string16 in = WideToUTF16(json_wide_cases[i].to_escape); - JsonDoubleQuote(in, false, &out); - EXPECT_EQ(std::string(json_wide_cases[i].escaped), out); + EscapeJSONString(in, false, &out); + EXPECT_EQ(std::string(cases[i].escaped), out); + EXPECT_TRUE(IsStringUTF8(out)); + + out = GetQuotedJSONString(in); + EXPECT_EQ("\"" + std::string(cases[i].escaped) + "\"", out); + EXPECT_TRUE(IsStringUTF8(out)); } - string16 in = WideToUTF16(json_wide_cases[0].to_escape); + string16 in = WideToUTF16(cases[0].to_escape); std::string out; - JsonDoubleQuote(in, false, &out); + EscapeJSONString(in, false, &out); + EXPECT_TRUE(IsStringUTF8(out)); // test quoting std::string out_quoted; - JsonDoubleQuote(in, true, &out_quoted); + EscapeJSONString(in, true, &out_quoted); EXPECT_EQ(out.length() + 2, out_quoted.length()); EXPECT_EQ(out_quoted.find(out), 1U); + EXPECT_TRUE(IsStringUTF8(out)); // now try with a NULL in the string string16 null_prepend = WideToUTF16(L"test"); null_prepend.push_back(0); in = null_prepend + in; std::string expected = "test\\u0000"; - expected += json_wide_cases[0].escaped; + expected += cases[0].escaped; out.clear(); - JsonDoubleQuote(in, false, &out); + EscapeJSONString(in, false, &out); EXPECT_EQ(expected, out); + EXPECT_TRUE(IsStringUTF8(out)); +} + +TEST(JSONStringEscapeTest, EscapeUTF16OutsideBMP) { + { + // {a, U+10300, !}, SMP. + string16 test; + test.push_back('a'); + test.push_back(0xD800); + test.push_back(0xDF00); + test.push_back('!'); + std::string actual; + EXPECT_TRUE(EscapeJSONString(test, false, &actual)); + EXPECT_EQ("a\xF0\x90\x8C\x80!", actual); + } + { + // {U+20021, U+2002B}, SIP. + string16 test; + test.push_back(0xD840); + test.push_back(0xDC21); + test.push_back(0xD840); + test.push_back(0xDC2B); + std::string actual; + EXPECT_TRUE(EscapeJSONString(test, false, &actual)); + EXPECT_EQ("\xF0\xA0\x80\xA1\xF0\xA0\x80\xAB", actual); + } + { + // {?, U+D800, @}, lone surrogate. + string16 test; + test.push_back('?'); + test.push_back(0xD800); + test.push_back('@'); + std::string actual; + EXPECT_FALSE(EscapeJSONString(test, false, &actual)); + EXPECT_EQ("?\xEF\xBF\xBD@", actual); + } +} + +TEST(JSONStringEscapeTest, EscapeBytes) { + const struct { + const char* to_escape; + const char* escaped; + } cases[] = { + {"b\x0f\x7f\xf0\xff!", "b\\u000F\\u007F\\u00F0\\u00FF!"}, + {"\xe5\xc4\x4f\x05\xb6\xfd\0", "\\u00E5\\u00C4O\\u0005\\u00B6\\u00FD"}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + std::string in = std::string(cases[i].to_escape); + EXPECT_FALSE(IsStringUTF8(in)); + + EXPECT_EQ(std::string(cases[i].escaped), + EscapeBytesAsInvalidJSONString(in, false)); + EXPECT_EQ("\"" + std::string(cases[i].escaped) + "\"", + EscapeBytesAsInvalidJSONString(in, true)); + } + + const char kEmbedNull[] = { '\xab', '\x39', '\0', '\x9f', '\xab' }; + std::string in(kEmbedNull, ARRAYSIZE_UNSAFE(kEmbedNull)); + EXPECT_FALSE(IsStringUTF8(in)); + EXPECT_EQ(std::string("\\u00AB9\\u0000\\u009F\\u00AB"), + EscapeBytesAsInvalidJSONString(in, false)); } } // namespace base diff --git a/chromium/base/linux_util.cc b/chromium/base/linux_util.cc index 5f9ecd43ab1..f8dd2d0088b 100644 --- a/chromium/base/linux_util.cc +++ b/chromium/base/linux_util.cc @@ -287,8 +287,7 @@ pid_t FindThreadIDWithSyscall(pid_t pid, const std::string& expected_data, continue; if (syscall_supported != NULL) *syscall_supported = true; - bool read_ret = - file_util::ReadFromFD(fd, syscall_data.get(), expected_data.length()); + bool read_ret = ReadFromFD(fd, syscall_data.get(), expected_data.length()); close(fd); if (!read_ret) continue; diff --git a/chromium/base/logging.h b/chromium/base/logging.h index 859f2602fc7..71f391f8aff 100644 --- a/chromium/base/logging.h +++ b/chromium/base/logging.h @@ -776,7 +776,12 @@ const LogSeverity LOG_DCHECK = LOG_INFO; #define DCHECK_GE(val1, val2) DCHECK_OP(GE, >=, val1, val2) #define DCHECK_GT(val1, val2) DCHECK_OP(GT, > , val1, val2) +#if defined(NDEBUG) && defined(OS_CHROMEOS) +#define NOTREACHED() LOG(ERROR) << "NOTREACHED() hit in " << \ + __FUNCTION__ << ". " +#else #define NOTREACHED() DCHECK(false) +#endif // Redefine the standard assert to use our nice log files #undef assert diff --git a/chromium/base/logging_unittest.cc b/chromium/base/logging_unittest.cc index bff10ebdf8d..4996abc2240 100644 --- a/chromium/base/logging_unittest.cc +++ b/chromium/base/logging_unittest.cc @@ -17,9 +17,11 @@ using ::testing::Return; // Needs to be global since log assert handlers can't maintain state. int log_sink_call_count = 0; +#if !LOGGING_IS_OFFICIAL_BUILD void LogSink(const std::string& str) { ++log_sink_call_count; } +#endif // !LOGGING_IS_OFFICIAL_BUILD // Class to make sure any manipulations we do to the min log level are // contained (i.e., do not affect other unit tests). @@ -167,7 +169,7 @@ TEST_F(LoggingTest, LoggingIsLazy) { } // Official builds have CHECKs directly call BreakDebugger. -#if !defined(LOGGING_IS_OFFICIAL_BUILD) +#if !LOGGING_IS_OFFICIAL_BUILD TEST_F(LoggingTest, CheckStreamsAreLazy) { MockLogSource mock_log_source, uncalled_mock_log_source; @@ -202,8 +204,7 @@ TEST_F(LoggingTest, DebugLoggingReleaseBehavior) { TEST_F(LoggingTest, DcheckStreamsAreLazy) { MockLogSource mock_log_source; EXPECT_CALL(mock_log_source, Log()).Times(0); -#if !defined(LOGGING_IS_OFFICIAL_BUILD) && defined(NDEBUG) && \ - !defined(DCHECK_ALWAYS_ON) +#if !LOGGING_IS_OFFICIAL_BUILD && defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON) // Unofficial release build without dcheck enabled. set_dcheck_state(DISABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS); DCHECK(mock_log_source.Log()) << mock_log_source.Log(); diff --git a/chromium/base/mac/authorization_util.h b/chromium/base/mac/authorization_util.h index b34348d175d..4629039c410 100644 --- a/chromium/base/mac/authorization_util.h +++ b/chromium/base/mac/authorization_util.h @@ -33,11 +33,20 @@ namespace base { namespace mac { -// Obtains an AuthorizationRef that can be used to run commands as root. If -// necessary, prompts the user for authentication. If the user is prompted, +// Obtains an AuthorizationRef for the rights indicated by |rights|. If +// necessary, prompts the user for authentication. If the user is prompted, // |prompt| will be used as the prompt string and an icon appropriate for the -// application will be displayed in a prompt dialog. Note that the system -// appends its own text to the prompt string. Returns NULL on failure. +// application will be displayed in a prompt dialog. Note that the system +// appends its own text to the prompt string. |extraFlags| will be ORed +// together with the default flags. Returns NULL on failure. +BASE_EXPORT +AuthorizationRef GetAuthorizationRightsWithPrompt( + AuthorizationRights* rights, + CFStringRef prompt, + AuthorizationFlags extraFlags); + +// Obtains an AuthorizationRef (using |GetAuthorizationRightsWithPrompt|) that +// can be used to run commands as root. BASE_EXPORT AuthorizationRef AuthorizationCreateToRunAsRoot(CFStringRef prompt); diff --git a/chromium/base/mac/authorization_util.mm b/chromium/base/mac/authorization_util.mm index c2925896209..6cb8de3f73b 100644 --- a/chromium/base/mac/authorization_util.mm +++ b/chromium/base/mac/authorization_util.mm @@ -22,7 +22,10 @@ namespace base { namespace mac { -AuthorizationRef AuthorizationCreateToRunAsRoot(CFStringRef prompt) { +AuthorizationRef GetAuthorizationRightsWithPrompt( + AuthorizationRights* rights, + CFStringRef prompt, + AuthorizationFlags extraFlags) { // Create an empty AuthorizationRef. ScopedAuthorizationRef authorization; OSStatus status = AuthorizationCreate(NULL, @@ -34,12 +37,11 @@ AuthorizationRef AuthorizationCreateToRunAsRoot(CFStringRef prompt) { return NULL; } - // Specify the "system.privilege.admin" right, which allows - // AuthorizationExecuteWithPrivileges to run commands as root. - AuthorizationItem right_items[] = { - {kAuthorizationRightExecute, 0, NULL, 0} - }; - AuthorizationRights rights = {arraysize(right_items), right_items}; + AuthorizationFlags flags = kAuthorizationFlagDefaults | + kAuthorizationFlagInteractionAllowed | + kAuthorizationFlagExtendRights | + kAuthorizationFlagPreAuthorize | + extraFlags; // product_logo_32.png is used instead of app.icns because Authorization // Services can't deal with .icns files. @@ -63,16 +65,12 @@ AuthorizationRef AuthorizationCreateToRunAsRoot(CFStringRef prompt) { AuthorizationEnvironment environment = {arraysize(environment_items), environment_items}; - AuthorizationFlags flags = kAuthorizationFlagDefaults | - kAuthorizationFlagInteractionAllowed | - kAuthorizationFlagExtendRights | - kAuthorizationFlagPreAuthorize; - status = AuthorizationCopyRights(authorization, - &rights, + rights, &environment, flags, NULL); + if (status != errAuthorizationSuccess) { if (status != errAuthorizationCanceled) { OSSTATUS_LOG(ERROR, status) << "AuthorizationCopyRights"; @@ -83,6 +81,17 @@ AuthorizationRef AuthorizationCreateToRunAsRoot(CFStringRef prompt) { return authorization.release(); } +AuthorizationRef AuthorizationCreateToRunAsRoot(CFStringRef prompt) { + // Specify the "system.privilege.admin" right, which allows + // AuthorizationExecuteWithPrivileges to run commands as root. + AuthorizationItem right_items[] = { + {kAuthorizationRightExecute, 0, NULL, 0} + }; + AuthorizationRights rights = {arraysize(right_items), right_items}; + + return GetAuthorizationRightsWithPrompt(&rights, prompt, 0); +} + OSStatus ExecuteWithPrivilegesAndGetPID(AuthorizationRef authorization, const char* tool_path, AuthorizationFlags options, diff --git a/chromium/base/mac/foundation_util.h b/chromium/base/mac/foundation_util.h index 447f7bf0d89..46ea3c324c3 100644 --- a/chromium/base/mac/foundation_util.h +++ b/chromium/base/mac/foundation_util.h @@ -16,10 +16,14 @@ #if defined(__OBJC__) #import <Foundation/Foundation.h> +@class NSFont; +@class UIFont; #else // __OBJC__ #include <CoreFoundation/CoreFoundation.h> class NSBundle; +class NSFont; class NSString; +class UIFont; #endif // __OBJC__ #if defined(OS_IOS) @@ -225,6 +229,12 @@ CF_TO_NS_CAST_DECL(CFWriteStream, NSOutputStream); CF_TO_NS_MUTABLE_CAST_DECL(String); CF_TO_NS_CAST_DECL(CFURL, NSURL); +#if defined(OS_IOS) +CF_TO_NS_CAST_DECL(CTFont, UIFont); +#else +CF_TO_NS_CAST_DECL(CTFont, NSFont); +#endif + #undef CF_TO_NS_CAST_DECL #undef CF_TO_NS_MUTABLE_CAST_DECL #undef OBJC_CPP_CLASS_DECL diff --git a/chromium/base/mac/foundation_util.mm b/chromium/base/mac/foundation_util.mm index 19df847a7ea..4e9b2248874 100644 --- a/chromium/base/mac/foundation_util.mm +++ b/chromium/base/mac/foundation_util.mm @@ -17,6 +17,7 @@ extern "C" { CFTypeID SecACLGetTypeID(); CFTypeID SecTrustedApplicationGetTypeID(); +Boolean _CFIsObjC(CFTypeID typeID, CFTypeRef obj); } // extern "C" #endif @@ -290,6 +291,31 @@ CF_TO_NS_CAST_DEFN(CFWriteStream, NSOutputStream); CF_TO_NS_MUTABLE_CAST_DEFN(String); CF_TO_NS_CAST_DEFN(CFURL, NSURL); +#if defined(OS_IOS) +CF_TO_NS_CAST_DEFN(CTFont, UIFont); +#else +// The NSFont/CTFont toll-free bridging is broken when it comes to type +// checking, so do some special-casing. +// http://www.openradar.me/15341349 rdar://15341349 +NSFont* CFToNSCast(CTFontRef cf_val) { + NSFont* ns_val = + const_cast<NSFont*>(reinterpret_cast<const NSFont*>(cf_val)); + DCHECK(!cf_val || + CTFontGetTypeID() == CFGetTypeID(cf_val) || + (_CFIsObjC(CTFontGetTypeID(), cf_val) && + [ns_val isKindOfClass:NSClassFromString(@"NSFont")])); + return ns_val; +} + +CTFontRef NSToCFCast(NSFont* ns_val) { + CTFontRef cf_val = reinterpret_cast<CTFontRef>(ns_val); + DCHECK(!cf_val || + CTFontGetTypeID() == CFGetTypeID(cf_val) || + [ns_val isKindOfClass:NSClassFromString(@"NSFont")]); + return cf_val; +} +#endif + #undef CF_TO_NS_CAST_DEFN #undef CF_TO_NS_MUTABLE_CAST_DEFN @@ -327,9 +353,41 @@ CF_CAST_DEFN(CFUUID); CF_CAST_DEFN(CGColor); -CF_CAST_DEFN(CTFont); CF_CAST_DEFN(CTRun); +#if defined(OS_IOS) +CF_CAST_DEFN(CTFont); +#else +// The NSFont/CTFont toll-free bridging is broken when it comes to type +// checking, so do some special-casing. +// http://www.openradar.me/15341349 rdar://15341349 +template<> CTFontRef +CFCast<CTFontRef>(const CFTypeRef& cf_val) { + if (cf_val == NULL) { + return NULL; + } + if (CFGetTypeID(cf_val) == CTFontGetTypeID()) { + return (CTFontRef)(cf_val); + } + + if (!_CFIsObjC(CTFontGetTypeID(), cf_val)) + return NULL; + + id<NSObject> ns_val = reinterpret_cast<id>(const_cast<void*>(cf_val)); + if ([ns_val isKindOfClass:NSClassFromString(@"NSFont")]) { + return (CTFontRef)(cf_val); + } + return NULL; +} + +template<> CTFontRef +CFCastStrict<CTFontRef>(const CFTypeRef& cf_val) { + CTFontRef rv = CFCast<CTFontRef>(cf_val); + DCHECK(cf_val == NULL || rv); + return rv; +} +#endif + #if !defined(OS_IOS) CF_CAST_DEFN(SecACL); CF_CAST_DEFN(SecTrustedApplication); diff --git a/chromium/base/mac/foundation_util_unittest.mm b/chromium/base/mac/foundation_util_unittest.mm index fc7dd099c57..3b72b1225a8 100644 --- a/chromium/base/mac/foundation_util_unittest.mm +++ b/chromium/base/mac/foundation_util_unittest.mm @@ -308,8 +308,7 @@ TEST(FoundationUtilTest, FilePathToNSString) { EXPECT_NSEQ(@"/a/b", FilePathToNSString(FilePath("/a/b"))); } -// http://crbug.com/173983 Fails consistently under Mac ASAN. -TEST(FoundationUtilTest, DISABLED_NSStringToFilePath) { +TEST(FoundationUtilTest, NSStringToFilePath) { EXPECT_EQ(FilePath(), NSStringToFilePath(nil)); EXPECT_EQ(FilePath(), NSStringToFilePath(@"")); EXPECT_EQ(FilePath("/a/b"), NSStringToFilePath(@"/a/b")); diff --git a/chromium/base/mac/mac_util.h b/chromium/base/mac/mac_util.h index 0605cbaf2c8..e827f37c9fb 100644 --- a/chromium/base/mac/mac_util.h +++ b/chromium/base/mac/mac_util.h @@ -92,13 +92,6 @@ BASE_EXPORT bool AmIForeground(); // Excludes the file given by |file_path| from being backed up by Time Machine. BASE_EXPORT bool SetFileBackupExclusion(const FilePath& file_path); -// Converts a NSImage to a CGImageRef. Normally, the system frameworks can do -// this fine, especially on 10.6. On 10.5, however, CGImage cannot handle -// converting a PDF-backed NSImage into a CGImageRef. This function will -// rasterize the PDF into a bitmap CGImage. The caller is responsible for -// releasing the return value. -BASE_EXPORT CGImageRef CopyNSImageToCGImage(NSImage* image); - // Checks if the current application is set as a Login Item, so it will launch // on Login. If a non-NULL pointer to is_hidden is passed, the Login Item also // is queried for the 'hide on launch' flag. @@ -142,12 +135,22 @@ BASE_EXPORT bool IsOSLionOrLater(); // Mountain Lion is Mac OS X 10.8, Darwin 12. BASE_EXPORT bool IsOSMountainLion(); +BASE_EXPORT bool IsOSMountainLionOrEarlier(); BASE_EXPORT bool IsOSMountainLionOrLater(); +// Mavericks is Mac OS X 10.9, Darwin 13. +BASE_EXPORT bool IsOSMavericks(); +BASE_EXPORT bool IsOSMavericksOrLater(); + // This should be infrequently used. It only makes sense to use this to avoid // codepaths that are very likely to break on future (unreleased, untested, // unborn) OS releases, or to log when the OS is newer than any known version. -BASE_EXPORT bool IsOSLaterThanMountainLion_DontCallThis(); +BASE_EXPORT bool IsOSLaterThanMavericks_DontCallThis(); + +// Inline functions that are redundant due to version ranges being mutually- +// exclusive. +inline bool IsOSLionOrEarlier() { return !IsOSMountainLionOrLater(); } +inline bool IsOSMountainLionOrEarlier() { return !IsOSMavericksOrLater(); } // When the deployment target is set, the code produced cannot run on earlier // OS releases. That enables some of the IsOS* family to be implemented as @@ -165,7 +168,6 @@ inline bool IsOSLionOrLater() { return true; } MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_7 #define BASE_MAC_MAC_UTIL_H_INLINED_GT_10_7 inline bool IsOSLion() { return false; } -inline bool IsOSLionOrEarlier() { return false; } #endif #if defined(MAC_OS_X_VERSION_10_8) && \ @@ -178,9 +180,19 @@ inline bool IsOSMountainLionOrLater() { return true; } MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_8 #define BASE_MAC_MAC_UTIL_H_INLINED_GT_10_8 inline bool IsOSMountainLion() { return false; } -inline bool IsOSLaterThanMountainLion_DontCallThis() { - return true; -} +#endif + +#if defined(MAC_OS_X_VERSION_10_9) && \ + MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_9 +#define BASE_MAC_MAC_UTIL_H_INLINED_GE_10_9 +inline bool IsOSMavericksOrLater() { return true; } +#endif + +#if defined(MAC_OS_X_VERSION_10_9) && \ + MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_9 +#define BASE_MAC_MAC_UTIL_H_INLINED_GT_10_9 +inline bool IsOSMavericks() { return false; } +inline bool IsOSLaterThanMavericks_DontCallThis() { return true; } #endif // Retrieve the system's model identifier string from the IOKit registry: diff --git a/chromium/base/mac/mac_util.mm b/chromium/base/mac/mac_util.mm index 89730745c79..c2565991abb 100644 --- a/chromium/base/mac/mac_util.mm +++ b/chromium/base/mac/mac_util.mm @@ -298,39 +298,6 @@ bool SetFileBackupExclusion(const FilePath& file_path) { return os_err == noErr; } -// Converts a NSImage to a CGImageRef. Normally, the system frameworks can do -// this fine, especially on 10.6. On 10.5, however, CGImage cannot handle -// converting a PDF-backed NSImage into a CGImageRef. This function will -// rasterize the PDF into a bitmap CGImage. The caller is responsible for -// releasing the return value. -CGImageRef CopyNSImageToCGImage(NSImage* image) { - // This is based loosely on http://www.cocoadev.com/index.pl?CGImageRef . - NSSize size = [image size]; - ScopedCFTypeRef<CGContextRef> context( - CGBitmapContextCreate(NULL, // Allow CG to allocate memory. - size.width, - size.height, - 8, // bitsPerComponent - 0, // bytesPerRow - CG will calculate by default. - [[NSColorSpace genericRGBColorSpace] CGColorSpace], - kCGBitmapByteOrder32Host | - kCGImageAlphaPremultipliedFirst)); - if (!context.get()) - return NULL; - - [NSGraphicsContext saveGraphicsState]; - [NSGraphicsContext setCurrentContext: - [NSGraphicsContext graphicsContextWithGraphicsPort:context.get() - flipped:NO]]; - [image drawInRect:NSMakeRect(0,0, size.width, size.height) - fromRect:NSZeroRect - operation:NSCompositeCopy - fraction:1.0]; - [NSGraphicsContext restoreGraphicsState]; - - return CGBitmapContextCreateImage(context); -} - bool CheckLoginItemStatus(bool* is_hidden) { ScopedCFTypeRef<LSSharedFileListItemRef> item(GetLoginItemForApp()); if (!item.get()) @@ -502,7 +469,7 @@ int MacOSXMinorVersionInternal() { // immediate death. CHECK(darwin_major_version >= 6); int mac_os_x_minor_version = darwin_major_version - 4; - DLOG_IF(WARNING, darwin_major_version > 12) << "Assuming Darwin " + DLOG_IF(WARNING, darwin_major_version > 13) << "Assuming Darwin " << base::IntToString(darwin_major_version) << " is Mac OS X 10." << base::IntToString(mac_os_x_minor_version); @@ -520,6 +487,7 @@ enum { SNOW_LEOPARD_MINOR_VERSION = 6, LION_MINOR_VERSION = 7, MOUNTAIN_LION_MINOR_VERSION = 8, + MAVERICKS_MINOR_VERSION = 9, }; } // namespace @@ -536,12 +504,6 @@ bool IsOSLion() { } #endif -#if !defined(BASE_MAC_MAC_UTIL_H_INLINED_GT_10_7) -bool IsOSLionOrEarlier() { - return MacOSXMinorVersion() <= LION_MINOR_VERSION; -} -#endif - #if !defined(BASE_MAC_MAC_UTIL_H_INLINED_GE_10_7) bool IsOSLionOrLater() { return MacOSXMinorVersion() >= LION_MINOR_VERSION; @@ -560,9 +522,21 @@ bool IsOSMountainLionOrLater() { } #endif -#if !defined(BASE_MAC_MAC_UTIL_H_INLINED_GT_10_8) -bool IsOSLaterThanMountainLion_DontCallThis() { - return MacOSXMinorVersion() > MOUNTAIN_LION_MINOR_VERSION; +#if !defined(BASE_MAC_MAC_UTIL_H_INLINED_GT_10_9) +bool IsOSMavericks() { + return MacOSXMinorVersion() == MAVERICKS_MINOR_VERSION; +} +#endif + +#if !defined(BASE_MAC_MAC_UTIL_H_INLINED_GE_10_9) +bool IsOSMavericksOrLater() { + return MacOSXMinorVersion() >= MAVERICKS_MINOR_VERSION; +} +#endif + +#if !defined(BASE_MAC_MAC_UTIL_H_INLINED_GT_10_9) +bool IsOSLaterThanMavericks_DontCallThis() { + return MacOSXMinorVersion() > MAVERICKS_MINOR_VERSION; } #endif diff --git a/chromium/base/mac/mac_util_unittest.mm b/chromium/base/mac/mac_util_unittest.mm index d2467578704..1b56814abec 100644 --- a/chromium/base/mac/mac_util_unittest.mm +++ b/chromium/base/mac/mac_util_unittest.mm @@ -124,20 +124,6 @@ TEST_F(MacUtilTest, TestExcludeFileFromBackups) { EXPECT_FALSE(excluded_by_path); } -TEST_F(MacUtilTest, CopyNSImageToCGImage) { - base::scoped_nsobject<NSImage> nsImage( - [[NSImage alloc] initWithSize:NSMakeSize(20, 20)]); - [nsImage lockFocus]; - [[NSColor redColor] set]; - NSRect rect = NSZeroRect; - rect.size = [nsImage size]; - NSRectFill(rect); - [nsImage unlockFocus]; - - ScopedCFTypeRef<CGImageRef> cgImage(CopyNSImageToCGImage(nsImage.get())); - EXPECT_TRUE(cgImage.get()); -} - TEST_F(MacUtilTest, NSObjectRetainRelease) { base::scoped_nsobject<NSArray> array( [[NSArray alloc] initWithObjects:@"foo", nil]); @@ -161,26 +147,46 @@ TEST_F(MacUtilTest, IsOSEllipsis) { EXPECT_TRUE(IsOSLionOrEarlier()); EXPECT_FALSE(IsOSLionOrLater()); EXPECT_FALSE(IsOSMountainLion()); + EXPECT_TRUE(IsOSMountainLionOrEarlier()); EXPECT_FALSE(IsOSMountainLionOrLater()); - EXPECT_FALSE(IsOSLaterThanMountainLion_DontCallThis()); + EXPECT_FALSE(IsOSMavericks()); + EXPECT_FALSE(IsOSMavericksOrLater()); + EXPECT_FALSE(IsOSLaterThanMavericks_DontCallThis()); } else if (minor == 7) { EXPECT_FALSE(IsOSSnowLeopard()); EXPECT_TRUE(IsOSLion()); EXPECT_TRUE(IsOSLionOrEarlier()); EXPECT_TRUE(IsOSLionOrLater()); EXPECT_FALSE(IsOSMountainLion()); + EXPECT_TRUE(IsOSMountainLionOrEarlier()); EXPECT_FALSE(IsOSMountainLionOrLater()); - EXPECT_FALSE(IsOSLaterThanMountainLion_DontCallThis()); + EXPECT_FALSE(IsOSMavericks()); + EXPECT_FALSE(IsOSMavericksOrLater()); + EXPECT_FALSE(IsOSLaterThanMavericks_DontCallThis()); } else if (minor == 8) { EXPECT_FALSE(IsOSSnowLeopard()); EXPECT_FALSE(IsOSLion()); EXPECT_FALSE(IsOSLionOrEarlier()); EXPECT_TRUE(IsOSLionOrLater()); EXPECT_TRUE(IsOSMountainLion()); + EXPECT_TRUE(IsOSMountainLionOrEarlier()); + EXPECT_TRUE(IsOSMountainLionOrLater()); + EXPECT_FALSE(IsOSMavericks()); + EXPECT_FALSE(IsOSMavericksOrLater()); + EXPECT_FALSE(IsOSLaterThanMavericks_DontCallThis()); + } else if (minor == 9) { + EXPECT_FALSE(IsOSSnowLeopard()); + EXPECT_FALSE(IsOSLion()); + EXPECT_FALSE(IsOSLionOrEarlier()); + EXPECT_TRUE(IsOSLionOrLater()); + EXPECT_FALSE(IsOSMountainLion()); + EXPECT_FALSE(IsOSMountainLionOrEarlier()); EXPECT_TRUE(IsOSMountainLionOrLater()); - EXPECT_FALSE(IsOSLaterThanMountainLion_DontCallThis()); + EXPECT_TRUE(IsOSMavericks()); + EXPECT_TRUE(IsOSMavericksOrLater()); + EXPECT_FALSE(IsOSLaterThanMavericks_DontCallThis()); } else { - // Not five, six, seven, or eight. Ah, ah, ah. + // Not five, six, seven, eight, or nine. Ah, ah, ah. EXPECT_TRUE(false); } } else { @@ -214,7 +220,7 @@ TEST_F(MacUtilTest, TestRemoveQuarantineAttribute) { ScopedTempDir temp_dir_; ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); FilePath dummy_folder_path = temp_dir_.path().Append("DummyFolder"); - ASSERT_TRUE(file_util::CreateDirectory(dummy_folder_path)); + ASSERT_TRUE(base::CreateDirectory(dummy_folder_path)); const char* quarantine_str = "0000;4b392bb2;Chromium;|org.chromium.Chromium"; const char* file_path_str = dummy_folder_path.value().c_str(); EXPECT_EQ(0, setxattr(file_path_str, "com.apple.quarantine", @@ -232,7 +238,7 @@ TEST_F(MacUtilTest, TestRemoveQuarantineAttributeTwice) { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); FilePath dummy_folder_path = temp_dir_.path().Append("DummyFolder"); const char* file_path_str = dummy_folder_path.value().c_str(); - ASSERT_TRUE(file_util::CreateDirectory(dummy_folder_path)); + ASSERT_TRUE(base::CreateDirectory(dummy_folder_path)); EXPECT_EQ(-1, getxattr(file_path_str, "com.apple.quarantine", NULL, 0, 0, 0)); // No quarantine attribute to begin with, but RemoveQuarantineAttribute still // succeeds because in the end the folder still doesn't have the quarantine diff --git a/chromium/base/mac/sdk_forward_declarations.h b/chromium/base/mac/sdk_forward_declarations.h index a2d4013723d..bbaf962b639 100644 --- a/chromium/base/mac/sdk_forward_declarations.h +++ b/chromium/base/mac/sdk_forward_declarations.h @@ -22,6 +22,7 @@ enum { NSEventPhaseChanged = 0x1 << 2, NSEventPhaseEnded = 0x1 << 3, NSEventPhaseCancelled = 0x1 << 4, + NSEventPhaseMayBegin = 0x1 << 5 }; typedef NSUInteger NSEventPhase; @@ -31,9 +32,19 @@ enum { }; typedef NSUInteger NSEventSwipeTrackingOptions; +enum { + NSWindowAnimationBehaviorDefault = 0, + NSWindowAnimationBehaviorNone = 2, + NSWindowAnimationBehaviorDocumentWindow = 3, + NSWindowAnimationBehaviorUtilityWindow = 4, + NSWindowAnimationBehaviorAlertPanel = 5 +}; +typedef NSInteger NSWindowAnimationBehavior; + @interface NSEvent (LionSDK) + (BOOL)isSwipeTrackingFromScrollEventsEnabled; +- (NSEventPhase)momentumPhase; - (NSEventPhase)phase; - (CGFloat)scrollingDeltaX; - (CGFloat)scrollingDeltaY; @@ -61,6 +72,8 @@ typedef NSUInteger NSEventSwipeTrackingOptions; @interface NSWindow (LionSDK) - (CGFloat)backingScaleFactor; +- (NSWindowAnimationBehavior)animationBehavior; +- (void)setAnimationBehavior:(NSWindowAnimationBehavior)newAnimationBehavior; @end #endif // MAC_OS_X_VERSION_10_7 diff --git a/chromium/base/memory/aligned_memory.cc b/chromium/base/memory/aligned_memory.cc index b6278aba2cf..5ec88b16b81 100644 --- a/chromium/base/memory/aligned_memory.cc +++ b/chromium/base/memory/aligned_memory.cc @@ -6,7 +6,7 @@ #include "base/logging.h" -#if defined(OS_ANDROID) || defined(OS_NACL) +#if defined(OS_ANDROID) #include <malloc.h> #endif @@ -19,13 +19,12 @@ void* AlignedAlloc(size_t size, size_t alignment) { void* ptr = NULL; #if defined(COMPILER_MSVC) ptr = _aligned_malloc(size, alignment); -// Both Android and NaCl technically support posix_memalign(), but do not expose -// it in the current version of the library headers used by Chrome. Luckily, -// memalign() on both platforms returns pointers which can safely be used with -// free(), so we can use it instead. Issues filed with each project for docs: +// Android technically supports posix_memalign(), but does not expose it in +// the current version of the library headers used by Chrome. Luckily, +// memalign() on Android returns pointers which can safely be used with +// free(), so we can use it instead. Issue filed to document this: // http://code.google.com/p/android/issues/detail?id=35391 -// http://code.google.com/p/chromium/issues/detail?id=138579 -#elif defined(OS_ANDROID) || defined(OS_NACL) +#elif defined(OS_ANDROID) ptr = memalign(alignment, size); #else if (posix_memalign(&ptr, alignment, size)) diff --git a/chromium/base/memory/discardable_memory.cc b/chromium/base/memory/discardable_memory.cc deleted file mode 100644 index 33f9e19cdee..00000000000 --- a/chromium/base/memory/discardable_memory.cc +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "base/memory/discardable_memory.h" - -#include "base/logging.h" - -namespace base { - -DiscardableMemory::DiscardableMemory() - : memory_(NULL), - size_(0), - is_locked_(false) -#if defined(OS_ANDROID) - , fd_(-1) -#endif // OS_ANDROID - { - DCHECK(Supported()); -} - -void* DiscardableMemory::Memory() const { - DCHECK(is_locked_); - return memory_; -} - -// Stub implementations for platforms that don't support discardable memory. - -#if !defined(OS_ANDROID) && !defined(OS_MACOSX) - -DiscardableMemory::~DiscardableMemory() { - NOTIMPLEMENTED(); -} - -// static -bool DiscardableMemory::Supported() { - return false; -} - -bool DiscardableMemory::InitializeAndLock(size_t size) { - NOTIMPLEMENTED(); - return false; -} - -LockDiscardableMemoryStatus DiscardableMemory::Lock() { - NOTIMPLEMENTED(); - return DISCARDABLE_MEMORY_FAILED; -} - -void DiscardableMemory::Unlock() { - NOTIMPLEMENTED(); -} - -// static -bool DiscardableMemory::PurgeForTestingSupported() { - return false; -} - -// static -void DiscardableMemory::PurgeForTesting() { - NOTIMPLEMENTED(); -} - -#endif // OS_* - -} // namespace base diff --git a/chromium/base/memory/discardable_memory.h b/chromium/base/memory/discardable_memory.h index d6e91b979d5..cbc2db630a8 100644 --- a/chromium/base/memory/discardable_memory.h +++ b/chromium/base/memory/discardable_memory.h @@ -8,6 +8,7 @@ #include "base/base_export.h" #include "base/basictypes.h" #include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" namespace base { @@ -37,44 +38,42 @@ enum LockDiscardableMemoryStatus { // - Because of memory alignment, the amount of memory allocated can be // larger than the requested memory size. It is not very efficient for // small allocations. +// - A discardable memory instance is not thread safe. It is the +// responsibility of users of discardable memory to ensure there are no +// races. // // References: // - Linux: http://lwn.net/Articles/452035/ // - Mac: http://trac.webkit.org/browser/trunk/Source/WebCore/platform/mac/PurgeableBufferMac.cpp // the comment starting with "vm_object_purgable_control" at // http://www.opensource.apple.com/source/xnu/xnu-792.13.8/osfmk/vm/vm_object.c +// +// Thread-safety: DiscardableMemory instances are not thread-safe. class BASE_EXPORT DiscardableMemory { public: - DiscardableMemory(); - - // If the discardable memory is locked, the destructor will unlock it. - // The opened file will also be closed after this. - ~DiscardableMemory(); + virtual ~DiscardableMemory() {} - // Check whether the system supports discardable memory. - static bool Supported(); + // Check whether the system supports discardable memory natively. Returns + // false if the support is emulated. + static bool SupportedNatively(); - // Initialize the DiscardableMemory object. On success, this function returns - // true and the memory is locked. This should only be called once. - // This call could fail because of platform-specific limitations and the user - // should stop using the DiscardableMemory afterwards. - bool InitializeAndLock(size_t size); + static scoped_ptr<DiscardableMemory> CreateLockedMemory(size_t size); - // Lock the memory so that it will not be purged by the system. Returns + // Locks the memory so that it will not be purged by the system. Returns // DISCARDABLE_MEMORY_SUCCESS on success. If the return value is // DISCARDABLE_MEMORY_FAILED then this object should be discarded and // a new one should be created. If the return value is // DISCARDABLE_MEMORY_PURGED then the memory is present but any data that // was in it is gone. - LockDiscardableMemoryStatus Lock() WARN_UNUSED_RESULT; + virtual LockDiscardableMemoryStatus Lock() WARN_UNUSED_RESULT = 0; - // Unlock the memory so that it can be purged by the system. Must be called + // Unlocks the memory so that it can be purged by the system. Must be called // after every successful lock call. - void Unlock(); + virtual void Unlock() = 0; - // Return the memory address held by this object. The object must be locked + // Returns the memory address held by this object. The object must be locked // before calling this. Otherwise, this will cause a DCHECK error. - void* Memory() const; + virtual void* Memory() const = 0; // Testing utility calls. @@ -85,32 +84,6 @@ class BASE_EXPORT DiscardableMemory { // Purge all discardable memory in the system. This call has global effects // across all running processes, so it should only be used for testing! static void PurgeForTesting(); - - private: -#if defined(OS_ANDROID) - // Maps the discardable memory into the caller's address space. - // Returns true on success, false otherwise. - bool Map(); - - // Unmaps the discardable memory from the caller's address space. - void Unmap(); - - // Reserve a file descriptor. When reaching the fd limit, this call returns - // false and initialization should fail. - bool ReserveFileDescriptor(); - - // Release a file descriptor so that others can reserve it. - void ReleaseFileDescriptor(); -#endif // OS_ANDROID - - void* memory_; - size_t size_; - bool is_locked_; -#if defined(OS_ANDROID) - int fd_; -#endif // OS_ANDROID - - DISALLOW_COPY_AND_ASSIGN(DiscardableMemory); }; } // namespace base diff --git a/chromium/base/memory/discardable_memory_allocator_android.cc b/chromium/base/memory/discardable_memory_allocator_android.cc new file mode 100644 index 00000000000..5e108176fe0 --- /dev/null +++ b/chromium/base/memory/discardable_memory_allocator_android.cc @@ -0,0 +1,418 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/discardable_memory_allocator_android.h" + +#include <algorithm> +#include <cmath> +#include <set> +#include <utility> + +#include "base/basictypes.h" +#include "base/containers/hash_tables.h" +#include "base/logging.h" +#include "base/memory/discardable_memory.h" +#include "base/memory/discardable_memory_android.h" +#include "base/memory/scoped_vector.h" +#include "base/synchronization/lock.h" +#include "base/threading/thread_checker.h" + +// The allocator consists of three parts (classes): +// - DiscardableMemoryAllocator: entry point of all allocations (through its +// Allocate() method) that are dispatched to the AshmemRegion instances (which +// it owns). +// - AshmemRegion: manages allocations and destructions inside a single large +// (e.g. 32 MBytes) ashmem region. +// - DiscardableAshmemChunk: class implementing the DiscardableMemory interface +// whose instances are returned to the client. DiscardableAshmemChunk lets the +// client seamlessly operate on a subrange of the ashmem region managed by +// AshmemRegion. + +namespace base { +namespace { + +// Only tolerate fragmentation in used chunks *caused by the client* (as opposed +// to the allocator when a free chunk is reused). The client can cause such +// fragmentation by e.g. requesting 4097 bytes. This size would be rounded up to +// 8192 by the allocator which would cause 4095 bytes of fragmentation (which is +// currently the maximum allowed). If the client requests 4096 bytes and a free +// chunk of 8192 bytes is available then the free chunk gets splitted into two +// pieces to minimize fragmentation (since 8192 - 4096 = 4096 which is greater +// than 4095). +// TODO(pliard): tune this if splitting chunks too often leads to performance +// issues. +const size_t kMaxChunkFragmentationBytes = 4096 - 1; + +} // namespace + +namespace internal { + +class DiscardableMemoryAllocator::DiscardableAshmemChunk + : public DiscardableMemory { + public: + // Note that |ashmem_region| must outlive |this|. + DiscardableAshmemChunk(AshmemRegion* ashmem_region, + int fd, + void* address, + size_t offset, + size_t size) + : ashmem_region_(ashmem_region), + fd_(fd), + address_(address), + offset_(offset), + size_(size), + locked_(true) { + } + + // Implemented below AshmemRegion since this requires the full definition of + // AshmemRegion. + virtual ~DiscardableAshmemChunk(); + + // DiscardableMemory: + virtual LockDiscardableMemoryStatus Lock() OVERRIDE { + DCHECK(!locked_); + locked_ = true; + return internal::LockAshmemRegion(fd_, offset_, size_, address_); + } + + virtual void Unlock() OVERRIDE { + DCHECK(locked_); + locked_ = false; + internal::UnlockAshmemRegion(fd_, offset_, size_, address_); + } + + virtual void* Memory() const OVERRIDE { + return address_; + } + + private: + AshmemRegion* const ashmem_region_; + const int fd_; + void* const address_; + const size_t offset_; + const size_t size_; + bool locked_; + + DISALLOW_COPY_AND_ASSIGN(DiscardableAshmemChunk); +}; + +class DiscardableMemoryAllocator::AshmemRegion { + public: + // Note that |allocator| must outlive |this|. + static scoped_ptr<AshmemRegion> Create( + size_t size, + const std::string& name, + DiscardableMemoryAllocator* allocator) { + int fd; + void* base; + if (!internal::CreateAshmemRegion(name.c_str(), size, &fd, &base)) + return scoped_ptr<AshmemRegion>(); + return make_scoped_ptr(new AshmemRegion(fd, size, base, allocator)); + } + + virtual ~AshmemRegion() { + const bool result = internal::CloseAshmemRegion(fd_, size_, base_); + DCHECK(result); + } + + // Returns a new instance of DiscardableMemory whose size is greater or equal + // than |actual_size| (which is expected to be greater or equal than + // |client_requested_size|). + // Allocation works as follows: + // 1) Reuse a previously freed chunk and return it if it succeeded. See + // ReuseFreeChunk_Locked() below for more information. + // 2) If no free chunk could be reused and the region is not big enough for + // the requested size then NULL is returned. + // 3) If there is enough room in the ashmem region then a new chunk is + // returned. This new chunk starts at |offset_| which is the end of the + // previously highest chunk in the region. + scoped_ptr<DiscardableMemory> Allocate_Locked(size_t client_requested_size, + size_t actual_size) { + DCHECK_LE(client_requested_size, actual_size); + allocator_->lock_.AssertAcquired(); + scoped_ptr<DiscardableMemory> memory = ReuseFreeChunk_Locked( + client_requested_size, actual_size); + if (memory) + return memory.Pass(); + if (size_ - offset_ < actual_size) { + // This region does not have enough space left to hold the requested size. + return scoped_ptr<DiscardableMemory>(); + } + void* const address = static_cast<char*>(base_) + offset_; + memory.reset( + new DiscardableAshmemChunk(this, fd_, address, offset_, actual_size)); + used_to_previous_chunk_map_.insert( + std::make_pair(address, highest_allocated_chunk_)); + highest_allocated_chunk_ = address; + offset_ += actual_size; + DCHECK_LE(offset_, size_); + return memory.Pass(); + } + + void OnChunkDeletion(void* chunk, size_t size) { + AutoLock auto_lock(allocator_->lock_); + MergeAndAddFreeChunk_Locked(chunk, size); + // Note that |this| might be deleted beyond this point. + } + + private: + struct FreeChunk { + FreeChunk(void* previous_chunk, void* start, size_t size) + : previous_chunk(previous_chunk), + start(start), + size(size) { + } + + void* const previous_chunk; + void* const start; + const size_t size; + + bool is_null() const { return !start; } + + bool operator<(const FreeChunk& other) const { + return size < other.size; + } + }; + + // Note that |allocator| must outlive |this|. + AshmemRegion(int fd, + size_t size, + void* base, + DiscardableMemoryAllocator* allocator) + : fd_(fd), + size_(size), + base_(base), + allocator_(allocator), + highest_allocated_chunk_(NULL), + offset_(0) { + DCHECK_GE(fd_, 0); + DCHECK_GE(size, kMinAshmemRegionSize); + DCHECK(base); + DCHECK(allocator); + } + + // Tries to reuse a previously freed chunk by doing a closest size match. + scoped_ptr<DiscardableMemory> ReuseFreeChunk_Locked( + size_t client_requested_size, + size_t actual_size) { + allocator_->lock_.AssertAcquired(); + const FreeChunk reused_chunk = RemoveFreeChunkFromIterator_Locked( + free_chunks_.lower_bound(FreeChunk(NULL, NULL, actual_size))); + if (reused_chunk.is_null()) + return scoped_ptr<DiscardableMemory>(); + + used_to_previous_chunk_map_.insert( + std::make_pair(reused_chunk.start, reused_chunk.previous_chunk)); + size_t reused_chunk_size = reused_chunk.size; + // |client_requested_size| is used below rather than |actual_size| to + // reflect the amount of bytes that would not be usable by the client (i.e. + // wasted). Using |actual_size| instead would not allow us to detect + // fragmentation caused by the client if he did misaligned allocations. + DCHECK_GE(reused_chunk.size, client_requested_size); + const size_t fragmentation_bytes = + reused_chunk.size - client_requested_size; + if (fragmentation_bytes > kMaxChunkFragmentationBytes) { + // Split the free chunk being recycled so that its unused tail doesn't get + // reused (i.e. locked) which would prevent it from being evicted under + // memory pressure. + reused_chunk_size = actual_size; + void* const new_chunk_start = + static_cast<char*>(reused_chunk.start) + actual_size; + DCHECK_GT(reused_chunk.size, actual_size); + const size_t new_chunk_size = reused_chunk.size - actual_size; + // Note that merging is not needed here since there can't be contiguous + // free chunks at this point. + AddFreeChunk_Locked( + FreeChunk(reused_chunk.start, new_chunk_start, new_chunk_size)); + } + const size_t offset = + static_cast<char*>(reused_chunk.start) - static_cast<char*>(base_); + internal::LockAshmemRegion( + fd_, offset, reused_chunk_size, reused_chunk.start); + scoped_ptr<DiscardableMemory> memory( + new DiscardableAshmemChunk(this, fd_, reused_chunk.start, offset, + reused_chunk_size)); + return memory.Pass(); + } + + // Makes the chunk identified with the provided arguments free and possibly + // merges this chunk with the previous and next contiguous ones. + // If the provided chunk is the only one used (and going to be freed) in the + // region then the internal ashmem region is closed so that the underlying + // physical pages are immediately released. + // Note that free chunks are unlocked therefore they can be reclaimed by the + // kernel if needed (under memory pressure) but they are not immediately + // released unfortunately since madvise(MADV_REMOVE) and + // fallocate(FALLOC_FL_PUNCH_HOLE) don't seem to work on ashmem. This might + // change in versions of kernel >=3.5 though. The fact that free chunks are + // not immediately released is the reason why we are trying to minimize + // fragmentation in order not to cause "artificial" memory pressure. + void MergeAndAddFreeChunk_Locked(void* chunk, size_t size) { + allocator_->lock_.AssertAcquired(); + size_t new_free_chunk_size = size; + // Merge with the previous chunk. + void* first_free_chunk = chunk; + DCHECK(!used_to_previous_chunk_map_.empty()); + const hash_map<void*, void*>::iterator previous_chunk_it = + used_to_previous_chunk_map_.find(chunk); + DCHECK(previous_chunk_it != used_to_previous_chunk_map_.end()); + void* previous_chunk = previous_chunk_it->second; + used_to_previous_chunk_map_.erase(previous_chunk_it); + if (previous_chunk) { + const FreeChunk free_chunk = RemoveFreeChunk_Locked(previous_chunk); + if (!free_chunk.is_null()) { + new_free_chunk_size += free_chunk.size; + first_free_chunk = previous_chunk; + // There should not be more contiguous previous free chunks. + DCHECK(!address_to_free_chunk_map_.count(free_chunk.previous_chunk)); + } + } + // Merge with the next chunk if free and present. + void* next_chunk = static_cast<char*>(chunk) + size; + const FreeChunk next_free_chunk = RemoveFreeChunk_Locked(next_chunk); + if (!next_free_chunk.is_null()) { + new_free_chunk_size += next_free_chunk.size; + // Same as above. + DCHECK(!address_to_free_chunk_map_.count(static_cast<char*>(next_chunk) + + next_free_chunk.size)); + } + const bool whole_ashmem_region_is_free = + used_to_previous_chunk_map_.empty(); + if (!whole_ashmem_region_is_free) { + AddFreeChunk_Locked( + FreeChunk(previous_chunk, first_free_chunk, new_free_chunk_size)); + return; + } + // The whole ashmem region is free thus it can be deleted. + DCHECK_EQ(base_, first_free_chunk); + DCHECK(free_chunks_.empty()); + DCHECK(address_to_free_chunk_map_.empty()); + DCHECK(used_to_previous_chunk_map_.empty()); + allocator_->DeleteAshmemRegion_Locked(this); // Deletes |this|. + } + + void AddFreeChunk_Locked(const FreeChunk& free_chunk) { + allocator_->lock_.AssertAcquired(); + const std::multiset<FreeChunk>::iterator it = free_chunks_.insert( + free_chunk); + address_to_free_chunk_map_.insert(std::make_pair(free_chunk.start, it)); + // Update the next used contiguous chunk, if any, since its previous chunk + // may have changed due to free chunks merging/splitting. + void* const next_used_contiguous_chunk = + static_cast<char*>(free_chunk.start) + free_chunk.size; + hash_map<void*, void*>::iterator previous_it = + used_to_previous_chunk_map_.find(next_used_contiguous_chunk); + if (previous_it != used_to_previous_chunk_map_.end()) + previous_it->second = free_chunk.start; + } + + // Finds and removes the free chunk, if any, whose start address is + // |chunk_start|. Returns a copy of the unlinked free chunk or a free chunk + // whose content is null if it was not found. + FreeChunk RemoveFreeChunk_Locked(void* chunk_start) { + allocator_->lock_.AssertAcquired(); + const hash_map< + void*, std::multiset<FreeChunk>::iterator>::iterator it = + address_to_free_chunk_map_.find(chunk_start); + if (it == address_to_free_chunk_map_.end()) + return FreeChunk(NULL, NULL, 0U); + return RemoveFreeChunkFromIterator_Locked(it->second); + } + + // Same as above but takes an iterator in. + FreeChunk RemoveFreeChunkFromIterator_Locked( + std::multiset<FreeChunk>::iterator free_chunk_it) { + allocator_->lock_.AssertAcquired(); + if (free_chunk_it == free_chunks_.end()) + return FreeChunk(NULL, NULL, 0U); + DCHECK(free_chunk_it != free_chunks_.end()); + const FreeChunk free_chunk(*free_chunk_it); + address_to_free_chunk_map_.erase(free_chunk_it->start); + free_chunks_.erase(free_chunk_it); + return free_chunk; + } + + const int fd_; + const size_t size_; + void* const base_; + DiscardableMemoryAllocator* const allocator_; + void* highest_allocated_chunk_; + // Points to the end of |highest_allocated_chunk_|. + size_t offset_; + // Allows free chunks recycling (lookup, insertion and removal) in O(log N). + // Note that FreeChunk values are indexed by their size and also note that + // multiple free chunks can have the same size (which is why multiset<> is + // used instead of e.g. set<>). + std::multiset<FreeChunk> free_chunks_; + // Used while merging free contiguous chunks to erase free chunks (from their + // start address) in constant time. Note that multiset<>::{insert,erase}() + // don't invalidate iterators (except the one for the element being removed + // obviously). + hash_map< + void*, std::multiset<FreeChunk>::iterator> address_to_free_chunk_map_; + // Maps the address of *used* chunks to the address of their previous + // contiguous chunk. + hash_map<void*, void*> used_to_previous_chunk_map_; + + DISALLOW_COPY_AND_ASSIGN(AshmemRegion); +}; + +DiscardableMemoryAllocator::DiscardableAshmemChunk::~DiscardableAshmemChunk() { + if (locked_) + internal::UnlockAshmemRegion(fd_, offset_, size_, address_); + ashmem_region_->OnChunkDeletion(address_, size_); +} + +DiscardableMemoryAllocator::DiscardableMemoryAllocator(const std::string& name) + : name_(name) { +} + +DiscardableMemoryAllocator::~DiscardableMemoryAllocator() { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(ashmem_regions_.empty()); +} + +scoped_ptr<DiscardableMemory> DiscardableMemoryAllocator::Allocate( + size_t size) { + const size_t aligned_size = internal::AlignToNextPage(size); + // TODO(pliard): make this function less naive by e.g. moving the free chunks + // multiset to the allocator itself in order to decrease even more + // fragmentation/speedup allocation. Note that there should not be more than a + // couple (=5) of AshmemRegion instances in practice though. + AutoLock auto_lock(lock_); + DCHECK_LE(ashmem_regions_.size(), 5U); + for (ScopedVector<AshmemRegion>::iterator it = ashmem_regions_.begin(); + it != ashmem_regions_.end(); ++it) { + scoped_ptr<DiscardableMemory> memory( + (*it)->Allocate_Locked(size, aligned_size)); + if (memory) + return memory.Pass(); + } + scoped_ptr<AshmemRegion> new_region( + AshmemRegion::Create( + std::max(static_cast<size_t>(kMinAshmemRegionSize), aligned_size), + name_.c_str(), this)); + if (!new_region) { + // TODO(pliard): consider adding an histogram to see how often this happens. + return scoped_ptr<DiscardableMemory>(); + } + ashmem_regions_.push_back(new_region.release()); + return ashmem_regions_.back()->Allocate_Locked(size, aligned_size); +} + +void DiscardableMemoryAllocator::DeleteAshmemRegion_Locked( + AshmemRegion* region) { + lock_.AssertAcquired(); + // Note that there should not be more than a couple of ashmem region instances + // in |ashmem_regions_|. + DCHECK_LE(ashmem_regions_.size(), 5U); + const ScopedVector<AshmemRegion>::iterator it = std::find( + ashmem_regions_.begin(), ashmem_regions_.end(), region); + DCHECK_NE(ashmem_regions_.end(), it); + std::swap(*it, ashmem_regions_.back()); + ashmem_regions_.pop_back(); +} + +} // namespace internal +} // namespace base diff --git a/chromium/base/memory/discardable_memory_allocator_android.h b/chromium/base/memory/discardable_memory_allocator_android.h new file mode 100644 index 00000000000..7991656cff6 --- /dev/null +++ b/chromium/base/memory/discardable_memory_allocator_android.h @@ -0,0 +1,65 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MEMORY_DISCARDABLE_MEMORY_ALLOCATOR_H_ +#define BASE_MEMORY_DISCARDABLE_MEMORY_ALLOCATOR_H_ + +#include <string> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/synchronization/lock.h" +#include "base/threading/thread_checker.h" + +namespace base { + +class DiscardableMemory; + +namespace internal { + +// On Android ashmem is used to implement discardable memory. It is backed by a +// file (descriptor) thus is a limited resource. This allocator minimizes the +// problem by allocating large ashmem regions internally and returning smaller +// chunks to the client. +// Allocated chunks are systematically aligned on a page boundary therefore this +// allocator should not be used for small allocations. +// +// Threading: The allocator must be deleted on the thread it was constructed on +// although its Allocate() method can be invoked on any thread. See +// discardable_memory.h for DiscardableMemory's threading guarantees. +class BASE_EXPORT_PRIVATE DiscardableMemoryAllocator { + public: + // Exposed for testing. + enum { + kMinAshmemRegionSize = 32 * 1024 * 1024, + }; + + // Note that |name| is only used for debugging/measurement purposes. + explicit DiscardableMemoryAllocator(const std::string& name); + ~DiscardableMemoryAllocator(); + + // Note that the allocator must outlive the returned DiscardableMemory + // instance. + scoped_ptr<DiscardableMemory> Allocate(size_t size); + + private: + class AshmemRegion; + class DiscardableAshmemChunk; + + void DeleteAshmemRegion_Locked(AshmemRegion* region); + + base::ThreadChecker thread_checker_; + const std::string name_; + base::Lock lock_; + ScopedVector<AshmemRegion> ashmem_regions_; + + DISALLOW_COPY_AND_ASSIGN(DiscardableMemoryAllocator); +}; + +} // namespace internal +} // namespace base + +#endif // BASE_MEMORY_DISCARDABLE_MEMORY_ALLOCATOR_H_ diff --git a/chromium/base/memory/discardable_memory_allocator_android_unittest.cc b/chromium/base/memory/discardable_memory_allocator_android_unittest.cc new file mode 100644 index 00000000000..97cf5d45f72 --- /dev/null +++ b/chromium/base/memory/discardable_memory_allocator_android_unittest.cc @@ -0,0 +1,232 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/discardable_memory_allocator_android.h" + +#include <sys/types.h> +#include <unistd.h> + +#include "base/memory/discardable_memory.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/stringprintf.h" +#include "build/build_config.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace internal { + +const char kAllocatorName[] = "allocator-for-testing"; + +const size_t kPageSize = 4096; +const size_t kMinAshmemRegionSize = + DiscardableMemoryAllocator::kMinAshmemRegionSize; + +class DiscardableMemoryAllocatorTest : public testing::Test { + protected: + DiscardableMemoryAllocatorTest() : allocator_(kAllocatorName) {} + + DiscardableMemoryAllocator allocator_; +}; + +void WriteToDiscardableMemory(DiscardableMemory* memory, size_t size) { + // Write to the first and the last pages only to avoid paging in up to 64 + // MBytes. + static_cast<char*>(memory->Memory())[0] = 'a'; + static_cast<char*>(memory->Memory())[size - 1] = 'a'; +} + +TEST_F(DiscardableMemoryAllocatorTest, Basic) { + const size_t size = 128; + scoped_ptr<DiscardableMemory> memory(allocator_.Allocate(size)); + ASSERT_TRUE(memory); + WriteToDiscardableMemory(memory.get(), size); +} + +TEST_F(DiscardableMemoryAllocatorTest, LargeAllocation) { + // Note that large allocations should just use DiscardableMemoryAndroidSimple + // instead. + const size_t size = 64 * 1024 * 1024; + scoped_ptr<DiscardableMemory> memory(allocator_.Allocate(size)); + ASSERT_TRUE(memory); + WriteToDiscardableMemory(memory.get(), size); +} + +TEST_F(DiscardableMemoryAllocatorTest, ChunksArePageAligned) { + scoped_ptr<DiscardableMemory> memory(allocator_.Allocate(kPageSize)); + ASSERT_TRUE(memory); + EXPECT_EQ(0U, reinterpret_cast<uint64_t>(memory->Memory()) % kPageSize); + WriteToDiscardableMemory(memory.get(), kPageSize); +} + +TEST_F(DiscardableMemoryAllocatorTest, AllocateFreeAllocate) { + scoped_ptr<DiscardableMemory> memory(allocator_.Allocate(kPageSize)); + // Extra allocation that prevents the region from being deleted when |memory| + // gets deleted. + scoped_ptr<DiscardableMemory> memory_lock(allocator_.Allocate(kPageSize)); + ASSERT_TRUE(memory); + void* const address = memory->Memory(); + memory->Unlock(); // Tests that the reused chunk is being locked correctly. + memory.reset(); + memory = allocator_.Allocate(kPageSize); + ASSERT_TRUE(memory); + // The previously freed chunk should be reused. + EXPECT_EQ(address, memory->Memory()); + WriteToDiscardableMemory(memory.get(), kPageSize); +} + +TEST_F(DiscardableMemoryAllocatorTest, FreeingWholeAshmemRegionClosesAshmem) { + scoped_ptr<DiscardableMemory> memory(allocator_.Allocate(kPageSize)); + ASSERT_TRUE(memory); + const int kMagic = 0xdeadbeef; + *static_cast<int*>(memory->Memory()) = kMagic; + memory.reset(); + // The previous ashmem region should have been closed thus it should not be + // reused. + memory = allocator_.Allocate(kPageSize); + ASSERT_TRUE(memory); + EXPECT_NE(kMagic, *static_cast<const int*>(memory->Memory())); +} + +TEST_F(DiscardableMemoryAllocatorTest, AllocateUsesBestFitAlgorithm) { + scoped_ptr<DiscardableMemory> memory1(allocator_.Allocate(3 * kPageSize)); + ASSERT_TRUE(memory1); + scoped_ptr<DiscardableMemory> memory2(allocator_.Allocate(2 * kPageSize)); + ASSERT_TRUE(memory2); + scoped_ptr<DiscardableMemory> memory3(allocator_.Allocate(1 * kPageSize)); + ASSERT_TRUE(memory3); + void* const address_3 = memory3->Memory(); + memory1.reset(); + // Don't free |memory2| to avoid merging the 3 blocks together. + memory3.reset(); + memory1 = allocator_.Allocate(1 * kPageSize); + ASSERT_TRUE(memory1); + // The chunk whose size is closest to the requested size should be reused. + EXPECT_EQ(address_3, memory1->Memory()); + WriteToDiscardableMemory(memory1.get(), kPageSize); +} + +TEST_F(DiscardableMemoryAllocatorTest, MergeFreeChunks) { + scoped_ptr<DiscardableMemory> memory1(allocator_.Allocate(kPageSize)); + ASSERT_TRUE(memory1); + scoped_ptr<DiscardableMemory> memory2(allocator_.Allocate(kPageSize)); + ASSERT_TRUE(memory2); + scoped_ptr<DiscardableMemory> memory3(allocator_.Allocate(kPageSize)); + ASSERT_TRUE(memory3); + scoped_ptr<DiscardableMemory> memory4(allocator_.Allocate(kPageSize)); + ASSERT_TRUE(memory4); + void* const memory1_address = memory1->Memory(); + memory1.reset(); + memory3.reset(); + // Freeing |memory2| (located between memory1 and memory3) should merge the + // three free blocks together. + memory2.reset(); + memory1 = allocator_.Allocate(3 * kPageSize); + EXPECT_EQ(memory1_address, memory1->Memory()); +} + +TEST_F(DiscardableMemoryAllocatorTest, MergeFreeChunksAdvanced) { + scoped_ptr<DiscardableMemory> memory1(allocator_.Allocate(4 * kPageSize)); + ASSERT_TRUE(memory1); + scoped_ptr<DiscardableMemory> memory2(allocator_.Allocate(4 * kPageSize)); + ASSERT_TRUE(memory2); + void* const memory1_address = memory1->Memory(); + memory1.reset(); + memory1 = allocator_.Allocate(2 * kPageSize); + memory2.reset(); + // At this point, the region should be in this state: + // 8 KBytes (used), 24 KBytes (free). + memory2 = allocator_.Allocate(6 * kPageSize); + EXPECT_EQ( + static_cast<const char*>(memory2->Memory()), + static_cast<const char*>(memory1_address) + 2 * kPageSize); +} + +TEST_F(DiscardableMemoryAllocatorTest, MergeFreeChunksAdvanced2) { + scoped_ptr<DiscardableMemory> memory1(allocator_.Allocate(4 * kPageSize)); + ASSERT_TRUE(memory1); + scoped_ptr<DiscardableMemory> memory2(allocator_.Allocate(4 * kPageSize)); + ASSERT_TRUE(memory2); + void* const memory1_address = memory1->Memory(); + memory1.reset(); + memory1 = allocator_.Allocate(2 * kPageSize); + scoped_ptr<DiscardableMemory> memory3(allocator_.Allocate(2 * kPageSize)); + // At this point, the region should be in this state: + // 8 KBytes (used), 8 KBytes (used), 16 KBytes (used). + memory3.reset(); + memory2.reset(); + // At this point, the region should be in this state: + // 8 KBytes (used), 24 KBytes (free). + memory2 = allocator_.Allocate(6 * kPageSize); + EXPECT_EQ( + static_cast<const char*>(memory2->Memory()), + static_cast<const char*>(memory1_address) + 2 * kPageSize); +} + +TEST_F(DiscardableMemoryAllocatorTest, MergeFreeChunksAndDeleteAshmemRegion) { + scoped_ptr<DiscardableMemory> memory1(allocator_.Allocate(4 * kPageSize)); + ASSERT_TRUE(memory1); + scoped_ptr<DiscardableMemory> memory2(allocator_.Allocate(4 * kPageSize)); + ASSERT_TRUE(memory2); + memory1.reset(); + memory1 = allocator_.Allocate(2 * kPageSize); + scoped_ptr<DiscardableMemory> memory3(allocator_.Allocate(2 * kPageSize)); + // At this point, the region should be in this state: + // 8 KBytes (used), 8 KBytes (used), 16 KBytes (used). + memory1.reset(); + memory3.reset(); + // At this point, the region should be in this state: + // 8 KBytes (free), 8 KBytes (used), 8 KBytes (free). + const int kMagic = 0xdeadbeef; + *static_cast<int*>(memory2->Memory()) = kMagic; + memory2.reset(); + // The whole region should have been deleted. + memory2 = allocator_.Allocate(2 * kPageSize); + EXPECT_NE(kMagic, *static_cast<int*>(memory2->Memory())); +} + +TEST_F(DiscardableMemoryAllocatorTest, + TooLargeFreeChunksDontCauseTooMuchFragmentationWhenRecycled) { + // Keep |memory_1| below allocated so that the ashmem region doesn't get + // closed when |memory_2| is deleted. + scoped_ptr<DiscardableMemory> memory_1(allocator_.Allocate(64 * 1024)); + ASSERT_TRUE(memory_1); + scoped_ptr<DiscardableMemory> memory_2(allocator_.Allocate(32 * 1024)); + ASSERT_TRUE(memory_2); + void* const address = memory_2->Memory(); + memory_2.reset(); + const size_t size = 16 * 1024; + memory_2 = allocator_.Allocate(size); + ASSERT_TRUE(memory_2); + EXPECT_EQ(address, memory_2->Memory()); + WriteToDiscardableMemory(memory_2.get(), size); + scoped_ptr<DiscardableMemory> memory_3(allocator_.Allocate(size)); + // The unused tail (16 KBytes large) of the previously freed chunk should be + // reused. + EXPECT_EQ(static_cast<char*>(address) + size, memory_3->Memory()); + WriteToDiscardableMemory(memory_3.get(), size); +} + +TEST_F(DiscardableMemoryAllocatorTest, UseMultipleAshmemRegions) { + // Leave one page untouched at the end of the ashmem region. + const size_t size = kMinAshmemRegionSize - kPageSize; + scoped_ptr<DiscardableMemory> memory1(allocator_.Allocate(size)); + ASSERT_TRUE(memory1); + WriteToDiscardableMemory(memory1.get(), size); + + scoped_ptr<DiscardableMemory> memory2( + allocator_.Allocate(kMinAshmemRegionSize)); + ASSERT_TRUE(memory2); + WriteToDiscardableMemory(memory2.get(), kMinAshmemRegionSize); + // The last page of the first ashmem region should be used for this + // allocation. + scoped_ptr<DiscardableMemory> memory3(allocator_.Allocate(kPageSize)); + ASSERT_TRUE(memory3); + WriteToDiscardableMemory(memory3.get(), kPageSize); + EXPECT_EQ(memory3->Memory(), static_cast<char*>(memory1->Memory()) + size); +} + +} // namespace internal +} // namespace base diff --git a/chromium/base/memory/discardable_memory_android.cc b/chromium/base/memory/discardable_memory_android.cc index 73a25ae419e..7e84967055f 100644 --- a/chromium/base/memory/discardable_memory_android.cc +++ b/chromium/base/memory/discardable_memory_android.cc @@ -2,153 +2,248 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "base/memory/discardable_memory.h" +#include "base/memory/discardable_memory_android.h" #include <sys/mman.h> +#include <sys/resource.h> +#include <sys/time.h> #include <unistd.h> +#include <limits> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/file_util.h" #include "base/lazy_instance.h" #include "base/logging.h" -#include "base/posix/eintr_wrapper.h" +#include "base/memory/discardable_memory.h" +#include "base/memory/discardable_memory_allocator_android.h" #include "base/synchronization/lock.h" #include "third_party/ashmem/ashmem.h" +namespace base { namespace { -base::LazyInstance<base::Lock>::Leaky g_discardable_memory_lock = - LAZY_INSTANCE_INITIALIZER; +const size_t kPageSize = 4096; -// Total number of discardable memory in the process. -int g_num_discardable_memory = 0; +const char kAshmemAllocatorName[] = "DiscardableMemoryAllocator"; -// Upper limit on the number of discardable memory to avoid hitting file -// descriptor limit. -const int kDiscardableMemoryNumLimit = 128; +struct GlobalContext { + GlobalContext() + : ashmem_fd_limit(GetSoftFDLimit()), + allocator(kAshmemAllocatorName), + ashmem_fd_count_(0) { + } -} + const int ashmem_fd_limit; + internal::DiscardableMemoryAllocator allocator; + Lock lock; -namespace base { + int ashmem_fd_count() const { + lock.AssertAcquired(); + return ashmem_fd_count_; + } -// static -bool DiscardableMemory::Supported() { - return true; -} + void decrement_ashmem_fd_count() { + lock.AssertAcquired(); + --ashmem_fd_count_; + } -DiscardableMemory::~DiscardableMemory() { - if (is_locked_) - Unlock(); - // If fd_ is smaller than 0, initialization must have failed and - // g_num_discardable_memory is not incremented by the caller. - if (fd_ < 0) - return; - HANDLE_EINTR(close(fd_)); - fd_ = -1; - ReleaseFileDescriptor(); -} + void increment_ashmem_fd_count() { + lock.AssertAcquired(); + ++ashmem_fd_count_; + } -bool DiscardableMemory::ReserveFileDescriptor() { - base::AutoLock lock(g_discardable_memory_lock.Get()); - if (g_num_discardable_memory < kDiscardableMemoryNumLimit) { - ++g_num_discardable_memory; - return true; + private: + static int GetSoftFDLimit() { + struct rlimit limit_info; + if (getrlimit(RLIMIT_NOFILE, &limit_info) != 0) + return 128; + // Allow 25% of file descriptor capacity for ashmem. + return limit_info.rlim_cur / 4; } - return false; + + int ashmem_fd_count_; +}; + +LazyInstance<GlobalContext>::Leaky g_context = LAZY_INSTANCE_INITIALIZER; + +// This is the default implementation of DiscardableMemory on Android which is +// used when file descriptor usage is under the soft limit. When file descriptor +// usage gets too high the discardable memory allocator is used instead. See +// ShouldUseAllocator() below for more details. +class DiscardableMemoryAndroidSimple : public DiscardableMemory { + public: + DiscardableMemoryAndroidSimple(int fd, void* address, size_t size) + : fd_(fd), + memory_(address), + size_(size) { + DCHECK_GE(fd_, 0); + DCHECK(memory_); + } + + virtual ~DiscardableMemoryAndroidSimple() { + internal::CloseAshmemRegion(fd_, size_, memory_); + } + + // DiscardableMemory: + virtual LockDiscardableMemoryStatus Lock() OVERRIDE { + return internal::LockAshmemRegion(fd_, 0, size_, memory_); + } + + virtual void Unlock() OVERRIDE { + internal::UnlockAshmemRegion(fd_, 0, size_, memory_); + } + + virtual void* Memory() const OVERRIDE { + return memory_; + } + + private: + const int fd_; + void* const memory_; + const size_t size_; + + DISALLOW_COPY_AND_ASSIGN(DiscardableMemoryAndroidSimple); +}; + +int GetCurrentNumberOfAshmemFDs() { + AutoLock lock(g_context.Get().lock); + return g_context.Get().ashmem_fd_count(); } -void DiscardableMemory::ReleaseFileDescriptor() { - base::AutoLock lock(g_discardable_memory_lock.Get()); - --g_num_discardable_memory; - DCHECK_LE(0, g_num_discardable_memory); +// Returns whether the provided size can be safely page-aligned (without causing +// an overflow). +bool CheckSizeCanBeAlignedToNextPage(size_t size) { + return size <= std::numeric_limits<size_t>::max() - kPageSize + 1; } -bool DiscardableMemory::InitializeAndLock(size_t size) { - // When this function returns true, fd_ should be larger or equal than 0 - // and g_num_discardable_memory is incremented by 1. Otherwise, fd_ - // is less than 0 and g_num_discardable_memory is not incremented by - // the caller. - DCHECK_EQ(fd_, -1); - DCHECK(!memory_); - if (!ReserveFileDescriptor()) - return false; +} // namespace + +namespace internal { - size_ = size; - fd_ = ashmem_create_region("", size); +size_t AlignToNextPage(size_t size) { + DCHECK_EQ(static_cast<int>(kPageSize), getpagesize()); + DCHECK(CheckSizeCanBeAlignedToNextPage(size)); + const size_t mask = ~(kPageSize - 1); + return (size + kPageSize - 1) & mask; +} - if (fd_ < 0) { +bool CreateAshmemRegion(const char* name, + size_t size, + int* out_fd, + void** out_address) { + AutoLock lock(g_context.Get().lock); + if (g_context.Get().ashmem_fd_count() + 1 > g_context.Get().ashmem_fd_limit) + return false; + int fd = ashmem_create_region(name, size); + if (fd < 0) { DLOG(ERROR) << "ashmem_create_region() failed"; - ReleaseFileDescriptor(); return false; } + file_util::ScopedFD fd_closer(&fd); - int err = ashmem_set_prot_region(fd_, PROT_READ | PROT_WRITE); + const int err = ashmem_set_prot_region(fd, PROT_READ | PROT_WRITE); if (err < 0) { DLOG(ERROR) << "Error " << err << " when setting protection of ashmem"; - HANDLE_EINTR(close(fd_)); - fd_ = -1; - ReleaseFileDescriptor(); return false; } - if (!Map()) { - // Close the file descriptor in case of any initialization errors. - HANDLE_EINTR(close(fd_)); - fd_ = -1; - ReleaseFileDescriptor(); + // There is a problem using MAP_PRIVATE here. As we are constantly calling + // Lock() and Unlock(), data could get lost if they are not written to the + // underlying file when Unlock() gets called. + void* const address = mmap( + NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (address == MAP_FAILED) { + DPLOG(ERROR) << "Failed to map memory."; return false; } - is_locked_ = true; + ignore_result(fd_closer.release()); + g_context.Get().increment_ashmem_fd_count(); + *out_fd = fd; + *out_address = address; return true; } -LockDiscardableMemoryStatus DiscardableMemory::Lock() { - DCHECK_NE(fd_, -1); - DCHECK(!is_locked_); - - bool purged = false; - if (ashmem_pin_region(fd_, 0, 0) == ASHMEM_WAS_PURGED) - purged = true; - - if (!Map()) - return DISCARDABLE_MEMORY_FAILED; - - is_locked_ = true; - return purged ? DISCARDABLE_MEMORY_PURGED : DISCARDABLE_MEMORY_SUCCESS; +bool CloseAshmemRegion(int fd, size_t size, void* address) { + AutoLock lock(g_context.Get().lock); + g_context.Get().decrement_ashmem_fd_count(); + if (munmap(address, size) == -1) { + DPLOG(ERROR) << "Failed to unmap memory."; + close(fd); + return false; + } + return close(fd) == 0; } -void DiscardableMemory::Unlock() { - DCHECK_GE(fd_, 0); - DCHECK(is_locked_); +LockDiscardableMemoryStatus LockAshmemRegion(int fd, + size_t off, + size_t size, + const void* address) { + const int result = ashmem_pin_region(fd, off, size); + DCHECK_EQ(0, mprotect(address, size, PROT_READ | PROT_WRITE)); + return result == ASHMEM_WAS_PURGED ? + DISCARDABLE_MEMORY_PURGED : DISCARDABLE_MEMORY_SUCCESS; +} - Unmap(); - if (ashmem_unpin_region(fd_, 0, 0)) +bool UnlockAshmemRegion(int fd, size_t off, size_t size, const void* address) { + const int failed = ashmem_unpin_region(fd, off, size); + if (failed) DLOG(ERROR) << "Failed to unpin memory."; - is_locked_ = false; + // This allows us to catch accesses to unlocked memory. + DCHECK_EQ(0, mprotect(address, size, PROT_NONE)); + return !failed; } -bool DiscardableMemory::Map() { - DCHECK(!memory_); - // There is a problem using MAP_PRIVATE here. As we are constantly calling - // Lock() and Unlock(), data could get lost if they are not written to the - // underlying file when Unlock() gets called. - memory_ = mmap(NULL, size_, PROT_READ | PROT_WRITE, MAP_SHARED, fd_, 0); - if (memory_ == (void*)-1) { - DPLOG(ERROR) << "Failed to map memory."; - memory_ = NULL; - if (ashmem_unpin_region(fd_, 0, 0)) - DLOG(ERROR) << "Failed to unpin memory."; - return false; - } +} // namespace internal + +// static +bool DiscardableMemory::SupportedNatively() { return true; } -void DiscardableMemory::Unmap() { - DCHECK(memory_); - - if (-1 == munmap(memory_, size_)) - DPLOG(ERROR) << "Failed to unmap memory."; - - memory_ = NULL; +// Allocation can happen in two ways: +// - Each client-requested allocation is backed by an individual ashmem region. +// This allows deleting ashmem regions individually by closing the ashmem file +// descriptor. This is the default path that is taken when file descriptor usage +// allows us to do so or when the allocation size would require and entire +// ashmem region. +// - Allocations are performed by the global allocator when file descriptor +// usage gets too high. This still allows unpinning but does not allow deleting +// (i.e. releasing the physical pages backing) individual regions. +// +// TODO(pliard): consider tuning the size threshold used below. For instance we +// might want to make it a fraction of kMinAshmemRegionSize and also +// systematically have small allocations go through the allocator to let big +// allocations systematically go through individual ashmem regions. +// +// static +scoped_ptr<DiscardableMemory> DiscardableMemory::CreateLockedMemory( + size_t size) { + if (!CheckSizeCanBeAlignedToNextPage(size)) + return scoped_ptr<DiscardableMemory>(); + // Pinning & unpinning works with page granularity therefore align the size + // upfront. + const size_t aligned_size = internal::AlignToNextPage(size); + // Note that the following code is slightly racy. The worst that can happen in + // practice though is taking the wrong decision (e.g. using the allocator + // rather than DiscardableMemoryAndroidSimple). Moreover keeping the lock + // acquired for the whole allocation would cause a deadlock when the allocator + // tries to create an ashmem region. + const size_t kAllocatorRegionSize = + internal::DiscardableMemoryAllocator::kMinAshmemRegionSize; + GlobalContext* const global_context = g_context.Pointer(); + if (aligned_size >= kAllocatorRegionSize || + GetCurrentNumberOfAshmemFDs() < 0.9 * global_context->ashmem_fd_limit) { + int fd; + void* address; + if (internal::CreateAshmemRegion("", aligned_size, &fd, &address)) { + return scoped_ptr<DiscardableMemory>( + new DiscardableMemoryAndroidSimple(fd, address, aligned_size)); + } + } + return global_context->allocator.Allocate(size); } // static diff --git a/chromium/base/memory/discardable_memory_android.h b/chromium/base/memory/discardable_memory_android.h new file mode 100644 index 00000000000..9db78b33853 --- /dev/null +++ b/chromium/base/memory/discardable_memory_android.h @@ -0,0 +1,37 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Please use discardable_memory.h since this is just an internal file providing +// utility functions used both by discardable_memory_android.cc and +// discardable_memory_allocator_android.cc. + +#ifndef BASE_MEMORY_DISCARDABLE_MEMORY_ANDROID_H_ +#define BASE_MEMORY_DISCARDABLE_MEMORY_ANDROID_H_ + +#include "base/basictypes.h" +#include "base/memory/discardable_memory.h" + +namespace base { +namespace internal { + +size_t AlignToNextPage(size_t size); + +bool CreateAshmemRegion(const char* name, size_t size, int* fd, void** address); + +bool CloseAshmemRegion(int fd, size_t size, void* address); + +LockDiscardableMemoryStatus LockAshmemRegion(int fd, + size_t offset, + size_t size, + const void* address); + +bool UnlockAshmemRegion(int fd, + size_t offset, + size_t size, + const void* address); + +} // namespace internal +} // namespace base + +#endif // BASE_MEMORY_DISCARDABLE_MEMORY_ANDROID_H_ diff --git a/chromium/base/memory/discardable_memory_emulated.cc b/chromium/base/memory/discardable_memory_emulated.cc new file mode 100644 index 00000000000..ed7c42c750f --- /dev/null +++ b/chromium/base/memory/discardable_memory_emulated.cc @@ -0,0 +1,65 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/discardable_memory_emulated.h" + +#include "base/lazy_instance.h" +#include "base/memory/discardable_memory_provider.h" + +namespace base { + +namespace { + +base::LazyInstance<internal::DiscardableMemoryProvider>::Leaky g_provider = + LAZY_INSTANCE_INITIALIZER; + +} // namespace + +namespace internal { + +DiscardableMemoryEmulated::DiscardableMemoryEmulated(size_t size) + : is_locked_(false) { + g_provider.Pointer()->Register(this, size); +} + +DiscardableMemoryEmulated::~DiscardableMemoryEmulated() { + if (is_locked_) + Unlock(); + g_provider.Pointer()->Unregister(this); +} + +bool DiscardableMemoryEmulated::Initialize() { + return Lock() == DISCARDABLE_MEMORY_PURGED; +} + +LockDiscardableMemoryStatus DiscardableMemoryEmulated::Lock() { + DCHECK(!is_locked_); + + bool purged = false; + memory_ = g_provider.Pointer()->Acquire(this, &purged); + if (!memory_) + return DISCARDABLE_MEMORY_FAILED; + + is_locked_ = true; + return purged ? DISCARDABLE_MEMORY_PURGED : DISCARDABLE_MEMORY_SUCCESS; +} + +void DiscardableMemoryEmulated::Unlock() { + DCHECK(is_locked_); + g_provider.Pointer()->Release(this, memory_.Pass()); + is_locked_ = false; +} + +void* DiscardableMemoryEmulated::Memory() const { + DCHECK(memory_); + return memory_.get(); +} + +// static +void DiscardableMemoryEmulated::PurgeForTesting() { + g_provider.Pointer()->PurgeAll(); +} + +} // namespace internal +} // namespace base diff --git a/chromium/base/memory/discardable_memory_emulated.h b/chromium/base/memory/discardable_memory_emulated.h new file mode 100644 index 00000000000..bd0e834ed06 --- /dev/null +++ b/chromium/base/memory/discardable_memory_emulated.h @@ -0,0 +1,37 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MEMORY_DISCARDABLE_MEMORY_EMULATED_H_ +#define BASE_MEMORY_DISCARDABLE_MEMORY_EMULATED_H_ + +#include "base/memory/discardable_memory.h" + +namespace base { +namespace internal { + +class DiscardableMemoryEmulated : public DiscardableMemory { + public: + explicit DiscardableMemoryEmulated(size_t size); + virtual ~DiscardableMemoryEmulated(); + + static void PurgeForTesting(); + + bool Initialize(); + + // Overridden from DiscardableMemory: + virtual LockDiscardableMemoryStatus Lock() OVERRIDE; + virtual void Unlock() OVERRIDE; + virtual void* Memory() const OVERRIDE; + + private: + scoped_ptr<uint8, FreeDeleter> memory_; + bool is_locked_; + + DISALLOW_COPY_AND_ASSIGN(DiscardableMemoryEmulated); +}; + +} // namespace internal +} // namespace base + +#endif // BASE_MEMORY_DISCARDABLE_MEMORY_EMULATED_H_ diff --git a/chromium/base/memory/discardable_memory_linux.cc b/chromium/base/memory/discardable_memory_linux.cc new file mode 100644 index 00000000000..92e39e5e7d4 --- /dev/null +++ b/chromium/base/memory/discardable_memory_linux.cc @@ -0,0 +1,35 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/discardable_memory_emulated.h" + +namespace base { + +// static +bool DiscardableMemory::SupportedNatively() { + return false; +} + +// static +scoped_ptr<DiscardableMemory> DiscardableMemory::CreateLockedMemory( + size_t size) { + scoped_ptr<internal::DiscardableMemoryEmulated> memory( + new internal::DiscardableMemoryEmulated(size)); + if (!memory->Initialize()) + return scoped_ptr<DiscardableMemory>(); + + return memory.PassAs<DiscardableMemory>(); +} + +// static +bool DiscardableMemory::PurgeForTestingSupported() { + return true; +} + +// static +void DiscardableMemory::PurgeForTesting() { + internal::DiscardableMemoryEmulated::PurgeForTesting(); +} + +} // namespace base diff --git a/chromium/base/memory/discardable_memory_mac.cc b/chromium/base/memory/discardable_memory_mac.cc index 1dbb6ad1fd2..aa6823509d5 100644 --- a/chromium/base/memory/discardable_memory_mac.cc +++ b/chromium/base/memory/discardable_memory_mac.cc @@ -5,11 +5,14 @@ #include "base/memory/discardable_memory.h" #include <mach/mach.h> +#include <sys/mman.h> +#include "base/basictypes.h" +#include "base/compiler_specific.h" #include "base/logging.h" +#include "base/memory/scoped_ptr.h" namespace base { - namespace { // The VM subsystem allows tagging of memory and 240-255 is reserved for @@ -17,25 +20,68 @@ namespace { // weight of ~52). const int kDiscardableMemoryTag = VM_MAKE_TAG(252); -} // namespace - -// static -bool DiscardableMemory::Supported() { - return true; -} +class DiscardableMemoryMac : public DiscardableMemory { + public: + DiscardableMemoryMac(void* memory, size_t size) + : memory_(memory), + size_(size) { + DCHECK(memory_); + } -DiscardableMemory::~DiscardableMemory() { - if (memory_) { + virtual ~DiscardableMemoryMac() { vm_deallocate(mach_task_self(), reinterpret_cast<vm_address_t>(memory_), size_); } -} -bool DiscardableMemory::InitializeAndLock(size_t size) { - DCHECK(!memory_); - size_ = size; + virtual LockDiscardableMemoryStatus Lock() OVERRIDE { + DCHECK_EQ(0, mprotect(memory_, size_, PROT_READ | PROT_WRITE)); + int state = VM_PURGABLE_NONVOLATILE; + kern_return_t ret = vm_purgable_control( + mach_task_self(), + reinterpret_cast<vm_address_t>(memory_), + VM_PURGABLE_SET_STATE, + &state); + if (ret != KERN_SUCCESS) + return DISCARDABLE_MEMORY_FAILED; + + return state & VM_PURGABLE_EMPTY ? DISCARDABLE_MEMORY_PURGED + : DISCARDABLE_MEMORY_SUCCESS; + } + + virtual void Unlock() OVERRIDE { + int state = VM_PURGABLE_VOLATILE | VM_VOLATILE_GROUP_DEFAULT; + kern_return_t ret = vm_purgable_control( + mach_task_self(), + reinterpret_cast<vm_address_t>(memory_), + VM_PURGABLE_SET_STATE, + &state); + DCHECK_EQ(0, mprotect(memory_, size_, PROT_NONE)); + if (ret != KERN_SUCCESS) + DLOG(ERROR) << "Failed to unlock memory."; + } + + virtual void* Memory() const OVERRIDE { + return memory_; + } + + private: + void* const memory_; + const size_t size_; + DISALLOW_COPY_AND_ASSIGN(DiscardableMemoryMac); +}; + +} // namespace + +// static +bool DiscardableMemory::SupportedNatively() { + return true; +} + +// static +scoped_ptr<DiscardableMemory> DiscardableMemory::CreateLockedMemory( + size_t size) { vm_address_t buffer = 0; kern_return_t ret = vm_allocate(mach_task_self(), &buffer, @@ -43,49 +89,12 @@ bool DiscardableMemory::InitializeAndLock(size_t size) { VM_FLAGS_PURGABLE | VM_FLAGS_ANYWHERE | kDiscardableMemoryTag); - if (ret != KERN_SUCCESS) { DLOG(ERROR) << "vm_allocate() failed"; - return false; + return scoped_ptr<DiscardableMemory>(); } - - is_locked_ = true; - memory_ = reinterpret_cast<void*>(buffer); - return true; -} - -LockDiscardableMemoryStatus DiscardableMemory::Lock() { - DCHECK(!is_locked_); - - int state = VM_PURGABLE_NONVOLATILE; - kern_return_t ret = vm_purgable_control( - mach_task_self(), - reinterpret_cast<vm_address_t>(memory_), - VM_PURGABLE_SET_STATE, - &state); - - if (ret != KERN_SUCCESS) - return DISCARDABLE_MEMORY_FAILED; - - is_locked_ = true; - return state & VM_PURGABLE_EMPTY ? DISCARDABLE_MEMORY_PURGED - : DISCARDABLE_MEMORY_SUCCESS; -} - -void DiscardableMemory::Unlock() { - DCHECK(is_locked_); - - int state = VM_PURGABLE_VOLATILE | VM_VOLATILE_GROUP_DEFAULT; - kern_return_t ret = vm_purgable_control( - mach_task_self(), - reinterpret_cast<vm_address_t>(memory_), - VM_PURGABLE_SET_STATE, - &state); - - if (ret != KERN_SUCCESS) - DLOG(ERROR) << "Failed to unlock memory."; - - is_locked_ = false; + return scoped_ptr<DiscardableMemory>( + new DiscardableMemoryMac(reinterpret_cast<void*>(buffer), size)); } // static diff --git a/chromium/base/memory/discardable_memory_provider.cc b/chromium/base/memory/discardable_memory_provider.cc new file mode 100644 index 00000000000..1c84339eba7 --- /dev/null +++ b/chromium/base/memory/discardable_memory_provider.cc @@ -0,0 +1,215 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/discardable_memory_provider.h" + +#include "base/bind.h" +#include "base/containers/hash_tables.h" +#include "base/containers/mru_cache.h" +#include "base/debug/trace_event.h" +#include "base/synchronization/lock.h" +#include "base/sys_info.h" + +namespace base { +namespace internal { + +namespace { + +// This is admittedly pretty magical. It's approximately enough memory for two +// 2560x1600 images. +static const size_t kDefaultDiscardableMemoryLimit = 32 * 1024 * 1024; +static const size_t kDefaultBytesToReclaimUnderModeratePressure = + kDefaultDiscardableMemoryLimit / 2; + +} // namespace + +DiscardableMemoryProvider::DiscardableMemoryProvider() + : allocations_(AllocationMap::NO_AUTO_EVICT), + bytes_allocated_(0), + discardable_memory_limit_(kDefaultDiscardableMemoryLimit), + bytes_to_reclaim_under_moderate_pressure_( + kDefaultBytesToReclaimUnderModeratePressure), + memory_pressure_listener_( + base::Bind(&DiscardableMemoryProvider::NotifyMemoryPressure, + Unretained(this))) { +} + +DiscardableMemoryProvider::~DiscardableMemoryProvider() { + DCHECK(allocations_.empty()); + DCHECK_EQ(0u, bytes_allocated_); +} + +void DiscardableMemoryProvider::NotifyMemoryPressure( + MemoryPressureListener::MemoryPressureLevel pressure_level) { + switch (pressure_level) { + case MemoryPressureListener::MEMORY_PRESSURE_MODERATE: + Purge(); + return; + case MemoryPressureListener::MEMORY_PRESSURE_CRITICAL: + PurgeAll(); + return; + } + + NOTREACHED(); +} + +void DiscardableMemoryProvider::SetDiscardableMemoryLimit(size_t bytes) { + AutoLock lock(lock_); + discardable_memory_limit_ = bytes; + EnforcePolicyWithLockAcquired(); +} + +void DiscardableMemoryProvider::SetBytesToReclaimUnderModeratePressure( + size_t bytes) { + AutoLock lock(lock_); + bytes_to_reclaim_under_moderate_pressure_ = bytes; +} + +void DiscardableMemoryProvider::Register( + const DiscardableMemory* discardable, size_t bytes) { + AutoLock lock(lock_); + DCHECK(allocations_.Peek(discardable) == allocations_.end()); + allocations_.Put(discardable, Allocation(bytes)); +} + +void DiscardableMemoryProvider::Unregister( + const DiscardableMemory* discardable) { + AutoLock lock(lock_); + AllocationMap::iterator it = allocations_.Peek(discardable); + if (it == allocations_.end()) + return; + + if (it->second.memory) { + size_t bytes = it->second.bytes; + DCHECK_LE(bytes, bytes_allocated_); + bytes_allocated_ -= bytes; + free(it->second.memory); + } + allocations_.Erase(it); +} + +scoped_ptr<uint8, FreeDeleter> DiscardableMemoryProvider::Acquire( + const DiscardableMemory* discardable, + bool* purged) { + AutoLock lock(lock_); + // NB: |allocations_| is an MRU cache, and use of |Get| here updates that + // cache. + AllocationMap::iterator it = allocations_.Get(discardable); + CHECK(it != allocations_.end()); + + if (it->second.memory) { + scoped_ptr<uint8, FreeDeleter> memory(it->second.memory); + it->second.memory = NULL; + *purged = false; + return memory.Pass(); + } + + size_t bytes = it->second.bytes; + if (!bytes) + return scoped_ptr<uint8, FreeDeleter>(); + + if (discardable_memory_limit_) { + size_t limit = 0; + if (bytes < discardable_memory_limit_) + limit = discardable_memory_limit_ - bytes; + + PurgeLRUWithLockAcquiredUntilUsageIsWithin(limit); + } + + // Check for overflow. + if (std::numeric_limits<size_t>::max() - bytes < bytes_allocated_) + return scoped_ptr<uint8, FreeDeleter>(); + + scoped_ptr<uint8, FreeDeleter> memory(static_cast<uint8*>(malloc(bytes))); + if (!memory) + return scoped_ptr<uint8, FreeDeleter>(); + + bytes_allocated_ += bytes; + *purged = true; + return memory.Pass(); +} + +void DiscardableMemoryProvider::Release( + const DiscardableMemory* discardable, + scoped_ptr<uint8, FreeDeleter> memory) { + AutoLock lock(lock_); + // NB: |allocations_| is an MRU cache, and use of |Get| here updates that + // cache. + AllocationMap::iterator it = allocations_.Get(discardable); + CHECK(it != allocations_.end()); + + DCHECK(!it->second.memory); + it->second.memory = memory.release(); + + EnforcePolicyWithLockAcquired(); +} + +void DiscardableMemoryProvider::PurgeAll() { + AutoLock lock(lock_); + PurgeLRUWithLockAcquiredUntilUsageIsWithin(0); +} + +bool DiscardableMemoryProvider::IsRegisteredForTest( + const DiscardableMemory* discardable) const { + AutoLock lock(lock_); + AllocationMap::const_iterator it = allocations_.Peek(discardable); + return it != allocations_.end(); +} + +bool DiscardableMemoryProvider::CanBePurgedForTest( + const DiscardableMemory* discardable) const { + AutoLock lock(lock_); + AllocationMap::const_iterator it = allocations_.Peek(discardable); + return it != allocations_.end() && it->second.memory; +} + +size_t DiscardableMemoryProvider::GetBytesAllocatedForTest() const { + AutoLock lock(lock_); + return bytes_allocated_; +} + +void DiscardableMemoryProvider::Purge() { + AutoLock lock(lock_); + + if (bytes_to_reclaim_under_moderate_pressure_ == 0) + return; + + size_t limit = 0; + if (bytes_to_reclaim_under_moderate_pressure_ < bytes_allocated_) + limit = bytes_allocated_ - bytes_to_reclaim_under_moderate_pressure_; + + PurgeLRUWithLockAcquiredUntilUsageIsWithin(limit); +} + +void DiscardableMemoryProvider::PurgeLRUWithLockAcquiredUntilUsageIsWithin( + size_t limit) { + TRACE_EVENT1( + "base", + "DiscardableMemoryProvider::PurgeLRUWithLockAcquiredUntilUsageIsWithin", + "limit", limit); + + lock_.AssertAcquired(); + + for (AllocationMap::reverse_iterator it = allocations_.rbegin(); + it != allocations_.rend(); + ++it) { + if (bytes_allocated_ <= limit) + break; + if (!it->second.memory) + continue; + + size_t bytes = it->second.bytes; + DCHECK_LE(bytes, bytes_allocated_); + bytes_allocated_ -= bytes; + free(it->second.memory); + it->second.memory = NULL; + } +} + +void DiscardableMemoryProvider::EnforcePolicyWithLockAcquired() { + PurgeLRUWithLockAcquiredUntilUsageIsWithin(discardable_memory_limit_); +} + +} // namespace internal +} // namespace base diff --git a/chromium/base/memory/discardable_memory_provider.h b/chromium/base/memory/discardable_memory_provider.h new file mode 100644 index 00000000000..6c343c0d929 --- /dev/null +++ b/chromium/base/memory/discardable_memory_provider.h @@ -0,0 +1,143 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MEMORY_DISCARDABLE_MEMORY_PROVIDER_H_ +#define BASE_MEMORY_DISCARDABLE_MEMORY_PROVIDER_H_ + +#include "base/base_export.h" +#include "base/containers/hash_tables.h" +#include "base/containers/mru_cache.h" +#include "base/memory/memory_pressure_listener.h" +#include "base/synchronization/lock.h" + +namespace base { +class DiscardableMemory; +} // namespace base + +#if defined(COMPILER_GCC) +namespace BASE_HASH_NAMESPACE { +template <> +struct hash<const base::DiscardableMemory*> { + size_t operator()(const base::DiscardableMemory* ptr) const { + return hash<size_t>()(reinterpret_cast<size_t>(ptr)); + } +}; +} // namespace BASE_HASH_NAMESPACE +#endif // COMPILER + +namespace base { +namespace internal { + +// The DiscardableMemoryProvider manages a collection of emulated +// DiscardableMemory instances. It is used on platforms that do not support +// discardable memory natively. It keeps track of all DiscardableMemory +// instances (in case they need to be purged), and the total amount of +// allocated memory (in case this forces a purge). +// +// When notified of memory pressure, the provider either purges the LRU +// memory -- if the pressure is moderate -- or all discardable memory +// if the pressure is critical. +// +// NB - this class is an implementation detail. It has been exposed for testing +// purposes. You should not need to use this class directly. +class BASE_EXPORT_PRIVATE DiscardableMemoryProvider { + public: + DiscardableMemoryProvider(); + ~DiscardableMemoryProvider(); + + // The maximum number of bytes of discardable memory that may be allocated + // before we force a purge. If this amount is zero, it is interpreted as + // having no limit at all. + void SetDiscardableMemoryLimit(size_t bytes); + + // Sets the amount of memory to reclaim when we're under moderate pressure. + void SetBytesToReclaimUnderModeratePressure(size_t bytes); + + // Adds the given discardable memory to the provider's collection. + void Register(const DiscardableMemory* discardable, size_t bytes); + + // Removes the given discardable memory from the provider's collection. + void Unregister(const DiscardableMemory* discardable); + + // Returns NULL if an error occurred. Otherwise, returns the backing buffer + // and sets |purged| to indicate whether or not the backing buffer has been + // purged since last use. + scoped_ptr<uint8, FreeDeleter> Acquire( + const DiscardableMemory* discardable, bool* purged); + + // Release a previously acquired backing buffer. This gives the buffer back + // to the provider where it can be purged if necessary. + void Release(const DiscardableMemory* discardable, + scoped_ptr<uint8, FreeDeleter> memory); + + // Purges all discardable memory. + void PurgeAll(); + + // Returns true if discardable memory has been added to the provider's + // collection. This should only be used by tests. + bool IsRegisteredForTest(const DiscardableMemory* discardable) const; + + // Returns true if discardable memory can be purged. This should only + // be used by tests. + bool CanBePurgedForTest(const DiscardableMemory* discardable) const; + + // Returns total amount of allocated discardable memory. This should only + // be used by tests. + size_t GetBytesAllocatedForTest() const; + + private: + struct Allocation { + explicit Allocation(size_t bytes) + : bytes(bytes), + memory(NULL) { + } + + size_t bytes; + uint8* memory; + }; + typedef HashingMRUCache<const DiscardableMemory*, Allocation> AllocationMap; + + // This can be called as a hint that the system is under memory pressure. + void NotifyMemoryPressure( + MemoryPressureListener::MemoryPressureLevel pressure_level); + + // Purges |bytes_to_reclaim_under_moderate_pressure_| bytes of + // discardable memory. + void Purge(); + + // Purges least recently used memory until usage is less or equal to |limit|. + // Caller must acquire |lock_| prior to calling this function. + void PurgeLRUWithLockAcquiredUntilUsageIsWithin(size_t limit); + + // Ensures that we don't allocate beyond our memory limit. + // Caller must acquire |lock_| prior to calling this function. + void EnforcePolicyWithLockAcquired(); + + // Needs to be held when accessing members. + mutable Lock lock_; + + // A MRU cache of all allocated bits of discardable memory. Used for purging. + AllocationMap allocations_; + + // The total amount of allocated discardable memory. + size_t bytes_allocated_; + + // The maximum number of bytes of discardable memory that may be allocated + // before we assume moderate memory pressure. + size_t discardable_memory_limit_; + + // Under moderate memory pressure, we will purge this amount of memory. + size_t bytes_to_reclaim_under_moderate_pressure_; + + // Allows us to be respond when the system reports that it is under memory + // pressure. + MemoryPressureListener memory_pressure_listener_; + + DISALLOW_COPY_AND_ASSIGN(DiscardableMemoryProvider); +}; + +} // namespace internal +} // namespace base + +#endif // BASE_MEMORY_DISCARDABLE_MEMORY_PROVIDER_H_ diff --git a/chromium/base/memory/discardable_memory_provider_unittest.cc b/chromium/base/memory/discardable_memory_provider_unittest.cc new file mode 100644 index 00000000000..1559654c789 --- /dev/null +++ b/chromium/base/memory/discardable_memory_provider_unittest.cc @@ -0,0 +1,406 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/discardable_memory_provider.h" + +#include "base/bind.h" +#include "base/memory/discardable_memory.h" +#include "base/run_loop.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +class DiscardableMemoryProviderTestBase { + public: + class TestDiscardableMemory : public DiscardableMemory { + public: + TestDiscardableMemory( + internal::DiscardableMemoryProvider* provider, size_t size) + : provider_(provider), + is_locked_(false) { + provider_->Register(this, size); + } + + virtual ~TestDiscardableMemory() { + if (is_locked_) + Unlock(); + provider_->Unregister(this); + } + + // Overridden from DiscardableMemory: + virtual LockDiscardableMemoryStatus Lock() OVERRIDE { + DCHECK(!is_locked_); + + bool purged = false; + memory_ = provider_->Acquire(this, &purged); + if (!memory_) + return DISCARDABLE_MEMORY_FAILED; + + is_locked_ = true; + return purged ? DISCARDABLE_MEMORY_PURGED : DISCARDABLE_MEMORY_SUCCESS; + } + virtual void Unlock() OVERRIDE { + DCHECK(is_locked_); + provider_->Release(this, memory_.Pass()); + is_locked_ = false; + } + virtual void* Memory() const OVERRIDE { + DCHECK(memory_); + return memory_.get(); + } + + private: + internal::DiscardableMemoryProvider* provider_; + scoped_ptr<uint8, FreeDeleter> memory_; + bool is_locked_; + + DISALLOW_COPY_AND_ASSIGN(TestDiscardableMemory); + }; + + DiscardableMemoryProviderTestBase() + : message_loop_(MessageLoop::TYPE_IO), + provider_(new internal::DiscardableMemoryProvider) { + } + + protected: + bool IsRegistered(const DiscardableMemory* discardable) { + return provider_->IsRegisteredForTest(discardable); + } + + bool CanBePurged(const DiscardableMemory* discardable) { + return provider_->CanBePurgedForTest(discardable); + } + + size_t BytesAllocated() const { + return provider_->GetBytesAllocatedForTest(); + } + + void* Memory(const DiscardableMemory* discardable) const { + return discardable->Memory(); + } + + void SetDiscardableMemoryLimit(size_t bytes) { + provider_->SetDiscardableMemoryLimit(bytes); + } + + void SetBytesToReclaimUnderModeratePressure(size_t bytes) { + provider_->SetBytesToReclaimUnderModeratePressure(bytes); + } + + scoped_ptr<DiscardableMemory> CreateLockedMemory(size_t size) { + scoped_ptr<TestDiscardableMemory> memory( + new TestDiscardableMemory(provider_.get(), size)); + if (memory->Lock() != DISCARDABLE_MEMORY_PURGED) + return scoped_ptr<DiscardableMemory>(); + return memory.PassAs<DiscardableMemory>(); + } + + private: + MessageLoop message_loop_; + scoped_ptr<internal::DiscardableMemoryProvider> provider_; +}; + +class DiscardableMemoryProviderTest + : public DiscardableMemoryProviderTestBase, + public testing::Test { + public: + DiscardableMemoryProviderTest() {} +}; + +TEST_F(DiscardableMemoryProviderTest, CreateLockedMemory) { + size_t size = 1024; + const scoped_ptr<DiscardableMemory> discardable(CreateLockedMemory(size)); + EXPECT_TRUE(IsRegistered(discardable.get())); + EXPECT_NE(static_cast<void*>(NULL), Memory(discardable.get())); + EXPECT_EQ(1024u, BytesAllocated()); + EXPECT_FALSE(CanBePurged(discardable.get())); +} + +TEST_F(DiscardableMemoryProviderTest, CreateLockedMemoryZeroSize) { + size_t size = 0; + const scoped_ptr<DiscardableMemory> discardable(CreateLockedMemory(size)); + EXPECT_FALSE(discardable); + EXPECT_FALSE(IsRegistered(discardable.get())); + EXPECT_EQ(0u, BytesAllocated()); +} + +TEST_F(DiscardableMemoryProviderTest, LockAfterUnlock) { + size_t size = 1024; + const scoped_ptr<DiscardableMemory> discardable(CreateLockedMemory(size)); + EXPECT_TRUE(IsRegistered(discardable.get())); + EXPECT_NE(static_cast<void*>(NULL), Memory(discardable.get())); + EXPECT_EQ(1024u, BytesAllocated()); + EXPECT_FALSE(CanBePurged(discardable.get())); + + // Now unlock so we can lock later. + discardable->Unlock(); + EXPECT_TRUE(CanBePurged(discardable.get())); + + EXPECT_EQ(DISCARDABLE_MEMORY_SUCCESS, discardable->Lock()); + EXPECT_FALSE(CanBePurged(discardable.get())); +} + +TEST_F(DiscardableMemoryProviderTest, LockAfterPurge) { + size_t size = 1024; + const scoped_ptr<DiscardableMemory> discardable(CreateLockedMemory(size)); + EXPECT_TRUE(IsRegistered(discardable.get())); + EXPECT_NE(static_cast<void*>(NULL), Memory(discardable.get())); + EXPECT_EQ(1024u, BytesAllocated()); + EXPECT_FALSE(CanBePurged(discardable.get())); + + // Now unlock so we can lock later. + discardable->Unlock(); + EXPECT_TRUE(CanBePurged(discardable.get())); + + // Force the system to purge. + MemoryPressureListener::NotifyMemoryPressure( + MemoryPressureListener::MEMORY_PRESSURE_CRITICAL); + + // Required because ObserverListThreadSafe notifies via PostTask. + RunLoop().RunUntilIdle(); + + EXPECT_EQ(DISCARDABLE_MEMORY_PURGED, discardable->Lock()); + EXPECT_FALSE(CanBePurged(discardable.get())); +} + +TEST_F(DiscardableMemoryProviderTest, LockAfterPurgeAndCannotReallocate) { + size_t size = 1024; + const scoped_ptr<DiscardableMemory> discardable(CreateLockedMemory(size)); + EXPECT_TRUE(IsRegistered(discardable.get())); + EXPECT_NE(static_cast<void*>(NULL), Memory(discardable.get())); + EXPECT_EQ(1024u, BytesAllocated()); + EXPECT_FALSE(CanBePurged(discardable.get())); + + // Now unlock so we can lock later. + discardable->Unlock(); + EXPECT_TRUE(CanBePurged(discardable.get())); + + // Set max allowed allocation to 1 byte. This will make cause the memory + // to be purged. + SetDiscardableMemoryLimit(1); + + EXPECT_EQ(DISCARDABLE_MEMORY_PURGED, discardable->Lock()); + EXPECT_FALSE(CanBePurged(discardable.get())); +} + +TEST_F(DiscardableMemoryProviderTest, Overflow) { + { + size_t size = 1024; + const scoped_ptr<DiscardableMemory> discardable(CreateLockedMemory(size)); + EXPECT_TRUE(IsRegistered(discardable.get())); + EXPECT_NE(static_cast<void*>(NULL), Memory(discardable.get())); + EXPECT_EQ(1024u, BytesAllocated()); + + size_t massive_size = std::numeric_limits<size_t>::max(); + const scoped_ptr<DiscardableMemory> massive_discardable( + CreateLockedMemory(massive_size)); + EXPECT_FALSE(massive_discardable); + EXPECT_EQ(1024u, BytesAllocated()); + } + EXPECT_EQ(0u, BytesAllocated()); +} + +class PermutationTestData { + public: + PermutationTestData(unsigned d0, unsigned d1, unsigned d2) { + ordering_[0] = d0; + ordering_[1] = d1; + ordering_[2] = d2; + } + + const unsigned* ordering() const { return ordering_; } + + private: + unsigned ordering_[3]; +}; + +class DiscardableMemoryProviderPermutationTest + : public DiscardableMemoryProviderTestBase, + public testing::TestWithParam<PermutationTestData> { + public: + DiscardableMemoryProviderPermutationTest() {} + + protected: + // Use discardable memory in order specified by ordering parameter. + void CreateAndUseDiscardableMemory() { + for (int i = 0; i < 3; ++i) { + discardables_[i] = CreateLockedMemory(1024); + EXPECT_TRUE(discardables_[i]); + EXPECT_NE(static_cast<void*>(NULL), Memory(discardables_[i].get())); + discardables_[i]->Unlock(); + } + for (int i = 0; i < 3; ++i) { + int index = GetParam().ordering()[i]; + EXPECT_NE(DISCARDABLE_MEMORY_FAILED, discardables_[index]->Lock()); + // Leave i == 0 locked. + if (i > 0) + discardables_[index]->Unlock(); + } + } + + DiscardableMemory* discardable(unsigned position) { + return discardables_[GetParam().ordering()[position]].get(); + } + + private: + scoped_ptr<DiscardableMemory> discardables_[3]; +}; + +// Verify that memory was discarded in the correct order after applying +// memory pressure. +TEST_P(DiscardableMemoryProviderPermutationTest, LRUDiscardedModeratePressure) { + CreateAndUseDiscardableMemory(); + + SetBytesToReclaimUnderModeratePressure(1024); + MemoryPressureListener::NotifyMemoryPressure( + MemoryPressureListener::MEMORY_PRESSURE_MODERATE); + RunLoop().RunUntilIdle(); + + EXPECT_NE(DISCARDABLE_MEMORY_FAILED, discardable(2)->Lock()); + EXPECT_NE(DISCARDABLE_MEMORY_SUCCESS, discardable(1)->Lock()); + // 0 should still be locked. + EXPECT_NE(static_cast<void*>(NULL), Memory(discardable(0))); +} + +// Verify that memory was discarded in the correct order after changing +// memory limit. +TEST_P(DiscardableMemoryProviderPermutationTest, LRUDiscardedExceedLimit) { + CreateAndUseDiscardableMemory(); + + SetBytesToReclaimUnderModeratePressure(1024); + SetDiscardableMemoryLimit(2048); + + EXPECT_NE(DISCARDABLE_MEMORY_FAILED, discardable(2)->Lock()); + EXPECT_NE(DISCARDABLE_MEMORY_SUCCESS, discardable(1)->Lock()); + // 0 should still be locked. + EXPECT_NE(static_cast<void*>(NULL), Memory(discardable(0))); +} + +// Verify that no more memory than necessary was discarded after changing +// memory limit. +TEST_P(DiscardableMemoryProviderPermutationTest, LRUDiscardedAmount) { + SetBytesToReclaimUnderModeratePressure(2048); + SetDiscardableMemoryLimit(4096); + + CreateAndUseDiscardableMemory(); + + SetDiscardableMemoryLimit(2048); + + EXPECT_EQ(DISCARDABLE_MEMORY_SUCCESS, discardable(2)->Lock()); + EXPECT_EQ(DISCARDABLE_MEMORY_PURGED, discardable(1)->Lock()); + // 0 should still be locked. + EXPECT_NE(static_cast<void*>(NULL), Memory(discardable(0))); +} + +TEST_P(DiscardableMemoryProviderPermutationTest, + CriticalPressureFreesAllUnlocked) { + CreateAndUseDiscardableMemory(); + + MemoryPressureListener::NotifyMemoryPressure( + MemoryPressureListener::MEMORY_PRESSURE_CRITICAL); + RunLoop().RunUntilIdle(); + + for (int i = 0; i < 3; ++i) { + if (i == 0) + EXPECT_NE(static_cast<void*>(NULL), Memory(discardable(i))); + else + EXPECT_EQ(DISCARDABLE_MEMORY_PURGED, discardable(i)->Lock()); + } +} + +INSTANTIATE_TEST_CASE_P(DiscardableMemoryProviderPermutationTests, + DiscardableMemoryProviderPermutationTest, + ::testing::Values(PermutationTestData(0, 1, 2), + PermutationTestData(0, 2, 1), + PermutationTestData(1, 0, 2), + PermutationTestData(1, 2, 0), + PermutationTestData(2, 0, 1), + PermutationTestData(2, 1, 0))); + +TEST_F(DiscardableMemoryProviderTest, NormalDestruction) { + { + size_t size = 1024; + const scoped_ptr<DiscardableMemory> discardable(CreateLockedMemory(size)); + EXPECT_TRUE(IsRegistered(discardable.get())); + EXPECT_EQ(1024u, BytesAllocated()); + } + EXPECT_EQ(0u, BytesAllocated()); +} + +TEST_F(DiscardableMemoryProviderTest, DestructionWhileLocked) { + { + size_t size = 1024; + const scoped_ptr<DiscardableMemory> discardable(CreateLockedMemory(size)); + EXPECT_TRUE(IsRegistered(discardable.get())); + EXPECT_NE(static_cast<void*>(NULL), Memory(discardable.get())); + EXPECT_EQ(1024u, BytesAllocated()); + EXPECT_FALSE(CanBePurged(discardable.get())); + } + // Should have ignored the "locked" status and freed the discardable memory. + EXPECT_EQ(0u, BytesAllocated()); +} + +#if !defined(NDEBUG) && !defined(OS_ANDROID) && !defined(OS_IOS) +// Death tests are not supported with Android APKs. +TEST_F(DiscardableMemoryProviderTest, UnlockedMemoryAccessCrashesInDebugMode) { + size_t size = 1024; + const scoped_ptr<DiscardableMemory> discardable(CreateLockedMemory(size)); + EXPECT_TRUE(IsRegistered(discardable.get())); + EXPECT_NE(static_cast<void*>(NULL), Memory(discardable.get())); + EXPECT_EQ(1024u, BytesAllocated()); + EXPECT_FALSE(CanBePurged(discardable.get())); + discardable->Unlock(); + EXPECT_TRUE(CanBePurged(discardable.get())); + // We *must* die if we are asked to vend a pointer to unlocked memory. + EXPECT_DEATH(discardable->Memory(), ".*Check failed.*"); +} +#endif + +class ThreadedDiscardableMemoryProviderTest + : public DiscardableMemoryProviderTest { + public: + ThreadedDiscardableMemoryProviderTest() + : memory_usage_thread_("memory_usage_thread"), + thread_sync_(true, false) { + } + + virtual void SetUp() OVERRIDE { + memory_usage_thread_.Start(); + } + + virtual void TearDown() OVERRIDE { + memory_usage_thread_.Stop(); + } + + void UseMemoryHelper() { + size_t size = 1024; + const scoped_ptr<DiscardableMemory> discardable(CreateLockedMemory(size)); + EXPECT_TRUE(IsRegistered(discardable.get())); + EXPECT_NE(static_cast<void*>(NULL), Memory(discardable.get())); + discardable->Unlock(); + } + + void SignalHelper() { + thread_sync_.Signal(); + } + + Thread memory_usage_thread_; + WaitableEvent thread_sync_; +}; + +TEST_F(ThreadedDiscardableMemoryProviderTest, UseMemoryOnThread) { + memory_usage_thread_.message_loop()->PostTask( + FROM_HERE, + Bind(&ThreadedDiscardableMemoryProviderTest::UseMemoryHelper, + Unretained(this))); + memory_usage_thread_.message_loop()->PostTask( + FROM_HERE, + Bind(&ThreadedDiscardableMemoryProviderTest::SignalHelper, + Unretained(this))); + thread_sync_.Wait(); +} + +} // namespace base diff --git a/chromium/base/memory/discardable_memory_unittest.cc b/chromium/base/memory/discardable_memory_unittest.cc index 60d35820fbd..c9f67b2556f 100644 --- a/chromium/base/memory/discardable_memory_unittest.cc +++ b/chromium/base/memory/discardable_memory_unittest.cc @@ -3,59 +3,91 @@ // found in the LICENSE file. #include "base/memory/discardable_memory.h" + +#include <limits> + #include "testing/gtest/include/gtest/gtest.h" namespace base { -#if defined(OS_ANDROID) || defined(OS_MACOSX) +const size_t kSize = 1024; + +#if defined(OS_ANDROID) +TEST(DiscardableMemoryTest, TooLargeAllocationFails) { + const size_t kPageSize = 4096; + const size_t max_allowed_allocation_size = + std::numeric_limits<size_t>::max() - kPageSize + 1; + scoped_ptr<DiscardableMemory> memory( + DiscardableMemory::CreateLockedMemory(max_allowed_allocation_size + 1)); + // On certain platforms (e.g. Android), page-alignment would have caused an + // overflow resulting in a small allocation if the input size wasn't checked + // correctly. + ASSERT_FALSE(memory); +} +#endif + +TEST(DiscardableMemoryTest, SupportedNatively) { +#if defined(DISCARDABLE_MEMORY_ALWAYS_SUPPORTED_NATIVELY) + ASSERT_TRUE(DiscardableMemory::SupportedNatively()); +#else + // If we ever have a platform that decides at runtime if it can support + // discardable memory natively, then we'll have to add a 'never supported + // natively' define for this case. At present, if it's not always supported + // natively, it's never supported. + ASSERT_FALSE(DiscardableMemory::SupportedNatively()); +#endif +} + // Test Lock() and Unlock() functionalities. TEST(DiscardableMemoryTest, LockAndUnLock) { - ASSERT_TRUE(DiscardableMemory::Supported()); - - const size_t size = 1024; - - DiscardableMemory memory; - ASSERT_TRUE(memory.InitializeAndLock(size)); - void* addr = memory.Memory(); + const scoped_ptr<DiscardableMemory> memory( + DiscardableMemory::CreateLockedMemory(kSize)); + ASSERT_TRUE(memory); + void* addr = memory->Memory(); ASSERT_NE(static_cast<void*>(NULL), addr); - memory.Unlock(); + memory->Unlock(); // The system should have no reason to purge discardable blocks in this brief // interval, though technically speaking this might flake. - EXPECT_EQ(DISCARDABLE_MEMORY_SUCCESS, memory.Lock()); - addr = memory.Memory(); + EXPECT_EQ(DISCARDABLE_MEMORY_SUCCESS, memory->Lock()); + addr = memory->Memory(); ASSERT_NE(static_cast<void*>(NULL), addr); - memory.Unlock(); + memory->Unlock(); } // Test delete a discardable memory while it is locked. TEST(DiscardableMemoryTest, DeleteWhileLocked) { - ASSERT_TRUE(DiscardableMemory::Supported()); - - const size_t size = 1024; - - DiscardableMemory memory; - ASSERT_TRUE(memory.InitializeAndLock(size)); + const scoped_ptr<DiscardableMemory> memory( + DiscardableMemory::CreateLockedMemory(kSize)); + ASSERT_TRUE(memory); } -#if defined(OS_MACOSX) +#if !defined(OS_ANDROID) // Test forced purging. TEST(DiscardableMemoryTest, Purge) { - ASSERT_TRUE(DiscardableMemory::Supported()); ASSERT_TRUE(DiscardableMemory::PurgeForTestingSupported()); - const size_t size = 1024; - - DiscardableMemory memory; - ASSERT_TRUE(memory.InitializeAndLock(size)); - memory.Unlock(); + const scoped_ptr<DiscardableMemory> memory( + DiscardableMemory::CreateLockedMemory(kSize)); + ASSERT_TRUE(memory); + memory->Unlock(); DiscardableMemory::PurgeForTesting(); - EXPECT_EQ(DISCARDABLE_MEMORY_PURGED, memory.Lock()); + EXPECT_EQ(DISCARDABLE_MEMORY_PURGED, memory->Lock()); } -#endif // OS_MACOSX - -#endif // OS_* +#endif // !OS_ANDROID + +#if !defined(NDEBUG) && !defined(OS_ANDROID) +// Death tests are not supported with Android APKs. +TEST(DiscardableMemoryTest, UnlockedMemoryAccessCrashesInDebugMode) { + const scoped_ptr<DiscardableMemory> memory( + DiscardableMemory::CreateLockedMemory(kSize)); + ASSERT_TRUE(memory); + memory->Unlock(); + ASSERT_DEATH_IF_SUPPORTED( + { *static_cast<int*>(memory->Memory()) = 0xdeadbeef; }, ".*"); +} +#endif } diff --git a/chromium/base/memory/discardable_memory_win.cc b/chromium/base/memory/discardable_memory_win.cc new file mode 100644 index 00000000000..92e39e5e7d4 --- /dev/null +++ b/chromium/base/memory/discardable_memory_win.cc @@ -0,0 +1,35 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/discardable_memory_emulated.h" + +namespace base { + +// static +bool DiscardableMemory::SupportedNatively() { + return false; +} + +// static +scoped_ptr<DiscardableMemory> DiscardableMemory::CreateLockedMemory( + size_t size) { + scoped_ptr<internal::DiscardableMemoryEmulated> memory( + new internal::DiscardableMemoryEmulated(size)); + if (!memory->Initialize()) + return scoped_ptr<DiscardableMemory>(); + + return memory.PassAs<DiscardableMemory>(); +} + +// static +bool DiscardableMemory::PurgeForTestingSupported() { + return true; +} + +// static +void DiscardableMemory::PurgeForTesting() { + internal::DiscardableMemoryEmulated::PurgeForTesting(); +} + +} // namespace base diff --git a/chromium/base/memory/memory_pressure_listener.h b/chromium/base/memory/memory_pressure_listener.h index 7b8029dd712..90eb144f4b4 100644 --- a/chromium/base/memory/memory_pressure_listener.h +++ b/chromium/base/memory/memory_pressure_listener.h @@ -24,11 +24,8 @@ namespace base { // simply delete the listener object. The implementation guarantees // that the callback will always be called on the thread that created // the listener. -// If this is the same thread as the system is broadcasting the memory pressure -// event on, then it is guaranteed you're called synchronously within that -// broadcast and hence you should not do long-running garbage collection work. -// But conversely, if there's something that needs to be released before -// control is returned to system code, this is the place to do it. +// Note that even on the same thread, the callback is not guaranteed to be +// called synchronously within the system memory pressure broadcast. // Please see notes on memory_pressure_level_list.h: some levels are absolutely // critical, and if not enough memory is returned to the system, it'll // potentially kill the app, and then later the app will have to be diff --git a/chromium/base/memory/ref_counted.h b/chromium/base/memory/ref_counted.h index 0c6a71fdb3e..aae36b17c2f 100644 --- a/chromium/base/memory/ref_counted.h +++ b/chromium/base/memory/ref_counted.h @@ -251,7 +251,11 @@ class scoped_refptr { } T* get() const { return ptr_; } + + // Allow scoped_refptr<C> to be used in boolean expression + // and comparison operations. operator T*() const { return ptr_; } + T* operator->() const { assert(ptr_ != NULL); return ptr_; diff --git a/chromium/base/memory/ref_counted_memory.cc b/chromium/base/memory/ref_counted_memory.cc index b048a6e0d8d..b1deee11201 100644 --- a/chromium/base/memory/ref_counted_memory.cc +++ b/chromium/base/memory/ref_counted_memory.cc @@ -4,6 +4,8 @@ #include "base/memory/ref_counted_memory.h" +#include <stdlib.h> + #include "base/logging.h" namespace base { @@ -74,4 +76,22 @@ size_t RefCountedString::size() const { return data_.size(); } +RefCountedMallocedMemory::RefCountedMallocedMemory( + void* data, size_t length) + : data_(reinterpret_cast<unsigned char*>(data)), length_(length) { + DCHECK(data || length == 0); +} + +const unsigned char* RefCountedMallocedMemory::front() const { + return length_ ? data_ : NULL; +} + +size_t RefCountedMallocedMemory::size() const { + return length_; +} + +RefCountedMallocedMemory::~RefCountedMallocedMemory() { + free(data_); +} + } // namespace base diff --git a/chromium/base/memory/ref_counted_memory.h b/chromium/base/memory/ref_counted_memory.h index fd5e8a0b8fc..d2987c5d21b 100644 --- a/chromium/base/memory/ref_counted_memory.h +++ b/chromium/base/memory/ref_counted_memory.h @@ -113,6 +113,26 @@ class BASE_EXPORT RefCountedString : public RefCountedMemory { DISALLOW_COPY_AND_ASSIGN(RefCountedString); }; +// An implementation of RefCountedMemory that holds a chunk of memory +// previously allocated with malloc or calloc, and that therefore must be freed +// using free(). +class BASE_EXPORT RefCountedMallocedMemory : public base::RefCountedMemory { + public: + RefCountedMallocedMemory(void* data, size_t length); + + // Overridden from RefCountedMemory: + virtual const unsigned char* front() const OVERRIDE; + virtual size_t size() const OVERRIDE; + + private: + virtual ~RefCountedMallocedMemory(); + + unsigned char* data_; + size_t length_; + + DISALLOW_COPY_AND_ASSIGN(RefCountedMallocedMemory); +}; + } // namespace base #endif // BASE_MEMORY_REF_COUNTED_MEMORY_H_ diff --git a/chromium/base/memory/ref_counted_memory_unittest.cc b/chromium/base/memory/ref_counted_memory_unittest.cc index c6f2b9c3d1c..88c5b534851 100644 --- a/chromium/base/memory/ref_counted_memory_unittest.cc +++ b/chromium/base/memory/ref_counted_memory_unittest.cc @@ -42,6 +42,16 @@ TEST(RefCountedMemoryUnitTest, RefCountedString) { EXPECT_EQ('e', mem->front()[1]); } +TEST(RefCountedMemoryUnitTest, RefCountedMallocedMemory) { + void* data = malloc(6); + memcpy(data, "hello", 6); + + scoped_refptr<RefCountedMemory> mem = new RefCountedMallocedMemory(data, 6); + + EXPECT_EQ(6U, mem->size()); + EXPECT_EQ(0, memcmp("hello", mem->front(), 6)); +} + TEST(RefCountedMemoryUnitTest, Equals) { std::string s1("same"); scoped_refptr<RefCountedMemory> mem1 = RefCountedString::TakeString(&s1); diff --git a/chromium/base/memory/ref_counted_unittest.cc b/chromium/base/memory/ref_counted_unittest.cc index e8eb0fd90f6..7a033f3a2a7 100644 --- a/chromium/base/memory/ref_counted_unittest.cc +++ b/chromium/base/memory/ref_counted_unittest.cc @@ -60,3 +60,25 @@ TEST(RefCountedUnitTest, ScopedRefPtrToSelf) { check->SelfDestruct(); EXPECT_TRUE(ScopedRefPtrToSelf::was_destroyed()); } + +TEST(RefCountedUnitTest, ScopedRefPtrBooleanOperations) { + scoped_refptr<SelfAssign> p1 = new SelfAssign; + scoped_refptr<SelfAssign> p2; + + EXPECT_TRUE(p1); + EXPECT_FALSE(!p1); + + EXPECT_TRUE(!p2); + EXPECT_FALSE(p2); + + EXPECT_NE(p1, p2); + + SelfAssign* raw_p = new SelfAssign; + p2 = raw_p; + EXPECT_NE(p1, p2); + EXPECT_EQ(raw_p, p2); + + p2 = p1; + EXPECT_NE(raw_p, p2); + EXPECT_EQ(p1, p2); +} diff --git a/chromium/base/memory/scoped_ptr.h b/chromium/base/memory/scoped_ptr.h index c1fed9ae459..790fecce71e 100644 --- a/chromium/base/memory/scoped_ptr.h +++ b/chromium/base/memory/scoped_ptr.h @@ -68,11 +68,11 @@ // is different though because we are constructing a temporary on the return // line and thus can avoid needing to call Pass(). // -// Pass() properly handles upcast in assignment, i.e. you can assign -// scoped_ptr<Child> to scoped_ptr<Parent>: +// Pass() properly handles upcast in initialization, i.e. you can use a +// scoped_ptr<Child> to initialize a scoped_ptr<Parent>: // // scoped_ptr<Foo> foo(new Foo()); -// scoped_ptr<FooParent> parent = foo.Pass(); +// scoped_ptr<FooParent> parent(foo.Pass()); // // PassAs<>() should be used to upcast return value in return statement: // diff --git a/chromium/base/memory/scoped_vector.h b/chromium/base/memory/scoped_vector.h index 59144c0e82a..1b30f635ca1 100644 --- a/chromium/base/memory/scoped_vector.h +++ b/chromium/base/memory/scoped_vector.h @@ -8,6 +8,7 @@ #include <vector> #include "base/basictypes.h" +#include "base/logging.h" #include "base/move.h" #include "base/stl_util.h" @@ -64,6 +65,12 @@ class ScopedVector { void push_back(T* elem) { v_.push_back(elem); } + void pop_back() { + DCHECK(!empty()); + delete v_.back(); + v_.pop_back(); + } + std::vector<T*>& get() { return v_; } const std::vector<T*>& get() const { return v_; } void swap(std::vector<T*>& other) { v_.swap(other); } diff --git a/chromium/base/memory/scoped_vector_unittest.cc b/chromium/base/memory/scoped_vector_unittest.cc index 353b52c4e73..efcc04757cc 100644 --- a/chromium/base/memory/scoped_vector_unittest.cc +++ b/chromium/base/memory/scoped_vector_unittest.cc @@ -112,6 +112,18 @@ TEST(ScopedVectorTest, LifeCycleWatcher) { EXPECT_EQ(LC_DESTROYED, watcher.life_cycle_state()); } +TEST(ScopedVectorTest, PopBack) { + LifeCycleWatcher watcher; + EXPECT_EQ(LC_INITIAL, watcher.life_cycle_state()); + ScopedVector<LifeCycleObject> scoped_vector; + scoped_vector.push_back(watcher.NewLifeCycleObject()); + EXPECT_EQ(LC_CONSTRUCTED, watcher.life_cycle_state()); + EXPECT_TRUE(watcher.IsWatching(scoped_vector.back())); + scoped_vector.pop_back(); + EXPECT_EQ(LC_DESTROYED, watcher.life_cycle_state()); + EXPECT_TRUE(scoped_vector.empty()); +} + TEST(ScopedVectorTest, Clear) { LifeCycleWatcher watcher; EXPECT_EQ(LC_INITIAL, watcher.life_cycle_state()); diff --git a/chromium/base/memory/shared_memory.h b/chromium/base/memory/shared_memory.h index 23f6973374a..9007aede109 100644 --- a/chromium/base/memory/shared_memory.h +++ b/chromium/base/memory/shared_memory.h @@ -21,6 +21,7 @@ #if defined(OS_POSIX) #include "base/file_descriptor_posix.h" +#include "base/file_util.h" #endif namespace base { @@ -80,6 +81,11 @@ class BASE_EXPORT SharedMemory { // Create a new SharedMemory object from an existing, open // shared memory file. + // + // WARNING: This does not reduce the OS-level permissions on the handle; it + // only affects how the SharedMemory will be mmapped. Use + // ShareReadOnlyToProcess to drop permissions. TODO(jln,jyasskin): DCHECK + // that |read_only| matches the permissions of the handle. SharedMemory(SharedMemoryHandle handle, bool read_only); // Create a new SharedMemory object from an existing, open @@ -189,15 +195,41 @@ class BASE_EXPORT SharedMemory { // It is safe to call Close repeatedly. void Close(); + // Shares the shared memory to another process. Attempts to create a + // platform-specific new_handle which can be used in a remote process to read + // the shared memory file. new_handle is an output parameter to receive the + // handle for use in the remote process. + // + // |*this| must have been initialized using one of the Create*() or Open() + // methods. If it was constructed from a SharedMemoryHandle, this call will + // CHECK-fail. + // + // Returns true on success, false otherwise. + bool ShareReadOnlyToProcess(ProcessHandle process, + SharedMemoryHandle* new_handle) { + return ShareToProcessCommon(process, new_handle, false, SHARE_READONLY); + } + + // Logically equivalent to: + // bool ok = ShareReadOnlyToProcess(process, new_handle); + // Close(); + // return ok; + // Note that the memory is unmapped by calling this method, regardless of the + // return value. + bool GiveReadOnlyToProcess(ProcessHandle process, + SharedMemoryHandle* new_handle) { + return ShareToProcessCommon(process, new_handle, true, SHARE_READONLY); + } + // Shares the shared memory to another process. Attempts // to create a platform-specific new_handle which can be // used in a remote process to access the shared memory - // file. new_handle is an ouput parameter to receive + // file. new_handle is an output parameter to receive // the handle for use in the remote process. // Returns true on success, false otherwise. bool ShareToProcess(ProcessHandle process, SharedMemoryHandle* new_handle) { - return ShareToProcessCommon(process, new_handle, false); + return ShareToProcessCommon(process, new_handle, false, SHARE_CURRENT_MODE); } // Logically equivalent to: @@ -208,7 +240,7 @@ class BASE_EXPORT SharedMemory { // return value. bool GiveToProcess(ProcessHandle process, SharedMemoryHandle* new_handle) { - return ShareToProcessCommon(process, new_handle, true); + return ShareToProcessCommon(process, new_handle, true, SHARE_CURRENT_MODE); } // Locks the shared memory. @@ -232,19 +264,25 @@ class BASE_EXPORT SharedMemory { private: #if defined(OS_POSIX) && !defined(OS_NACL) - bool PrepareMapFile(FILE *fp); + bool PrepareMapFile(file_util::ScopedFILE fp, file_util::ScopedFD readonly); bool FilePathForMemoryName(const std::string& mem_name, FilePath* path); void LockOrUnlockCommon(int function); #endif + enum ShareMode { + SHARE_READONLY, + SHARE_CURRENT_MODE, + }; bool ShareToProcessCommon(ProcessHandle process, SharedMemoryHandle* new_handle, - bool close_self); + bool close_self, + ShareMode); #if defined(OS_WIN) std::wstring name_; HANDLE mapped_file_; #elif defined(OS_POSIX) int mapped_file_; + int readonly_mapped_file_; ino_t inode_; #endif size_t mapped_size_; diff --git a/chromium/base/memory/shared_memory_android.cc b/chromium/base/memory/shared_memory_android.cc index 3ca3c8fa360..25a65730cb7 100644 --- a/chromium/base/memory/shared_memory_android.cc +++ b/chromium/base/memory/shared_memory_android.cc @@ -37,6 +37,15 @@ bool SharedMemory::Create(const SharedMemoryCreateOptions& options) { DLOG(ERROR) << "Error " << err << " when setting protection of ashmem"; return false; } + + // Android doesn't appear to have a way to drop write access on an ashmem + // segment for a single descriptor. http://crbug.com/320865 + readonly_mapped_file_ = dup(mapped_file_); + if (-1 == readonly_mapped_file_) { + DPLOG(ERROR) << "dup() failed"; + return false; + } + requested_size_ = options.size; return true; diff --git a/chromium/base/memory/shared_memory_nacl.cc b/chromium/base/memory/shared_memory_nacl.cc index bc2a98dfdf6..93c10026191 100644 --- a/chromium/base/memory/shared_memory_nacl.cc +++ b/chromium/base/memory/shared_memory_nacl.cc @@ -140,7 +140,13 @@ void SharedMemory::Unlock() { bool SharedMemory::ShareToProcessCommon(ProcessHandle process, SharedMemoryHandle *new_handle, - bool close_self) { + bool close_self, + ShareMode share_mode) { + if (share_mode == SHARE_READONLY) { + // Untrusted code can't create descriptors or handles, which is needed to + // drop permissions. + return false; + } const int new_fd = dup(mapped_file_); if (new_fd < 0) { DPLOG(ERROR) << "dup() failed."; diff --git a/chromium/base/memory/shared_memory_posix.cc b/chromium/base/memory/shared_memory_posix.cc index e6745ea3a7c..35d8c7d35df 100644 --- a/chromium/base/memory/shared_memory_posix.cc +++ b/chromium/base/memory/shared_memory_posix.cc @@ -30,20 +30,20 @@ #include "third_party/ashmem/ashmem.h" #endif +using file_util::ScopedFD; +using file_util::ScopedFILE; + namespace base { namespace { -// Paranoia. Semaphores and shared memory segments should live in different -// namespaces, but who knows what's out there. -const char kSemaphoreSuffix[] = "-sem"; - LazyInstance<Lock>::Leaky g_thread_lock_ = LAZY_INSTANCE_INITIALIZER; } SharedMemory::SharedMemory() : mapped_file_(-1), + readonly_mapped_file_(-1), inode_(0), mapped_size_(0), memory_(NULL), @@ -53,6 +53,7 @@ SharedMemory::SharedMemory() SharedMemory::SharedMemory(SharedMemoryHandle handle, bool read_only) : mapped_file_(handle.fd), + readonly_mapped_file_(-1), inode_(0), mapped_size_(0), memory_(NULL), @@ -69,6 +70,7 @@ SharedMemory::SharedMemory(SharedMemoryHandle handle, bool read_only) SharedMemory::SharedMemory(SharedMemoryHandle handle, bool read_only, ProcessHandle process) : mapped_file_(handle.fd), + readonly_mapped_file_(-1), inode_(0), mapped_size_(0), memory_(NULL), @@ -96,7 +98,7 @@ SharedMemoryHandle SharedMemory::NULLHandle() { // static void SharedMemory::CloseHandle(const SharedMemoryHandle& handle) { DCHECK_GE(handle.fd, 0); - if (HANDLE_EINTR(close(handle.fd)) < 0) + if (close(handle.fd) < 0) DPLOG(ERROR) << "close"; } @@ -128,8 +130,10 @@ bool SharedMemory::Create(const SharedMemoryCreateOptions& options) { // and be deleted before they ever make it out to disk. base::ThreadRestrictions::ScopedAllowIO allow_io; - FILE *fp; + ScopedFILE fp; bool fix_size = true; + int readonly_fd_storage = -1; + ScopedFD readonly_fd(&readonly_fd_storage); FilePath path; if (options.name == NULL || options.name->empty()) { @@ -137,12 +141,18 @@ bool SharedMemory::Create(const SharedMemoryCreateOptions& options) { DCHECK(!options.open_existing); // Q: Why not use the shm_open() etc. APIs? // A: Because they're limited to 4mb on OS X. FFFFFFFUUUUUUUUUUU - fp = file_util::CreateAndOpenTemporaryShmemFile(&path, options.executable); + fp.reset(base::CreateAndOpenTemporaryShmemFile(&path, options.executable)); - // Deleting the file prevents anyone else from mapping it in (making it - // private), and prevents the need for cleanup (once the last fd is closed, - // it is truly freed). if (fp) { + // Also open as readonly so that we can ShareReadOnlyToProcess. + *readonly_fd = HANDLE_EINTR(open(path.value().c_str(), O_RDONLY)); + if (*readonly_fd < 0) { + DPLOG(ERROR) << "open(\"" << path.value() << "\", O_RDONLY) failed"; + fp.reset(); + } + // Deleting the file prevents anyone else from mapping it in (making it + // private), and prevents the need for cleanup (once the last fd is + // closed, it is truly freed). if (unlink(path.value().c_str())) PLOG(WARNING) << "unlink"; } @@ -179,32 +189,35 @@ bool SharedMemory::Create(const SharedMemoryCreateOptions& options) { sb.st_uid != effective_uid)) { LOG(ERROR) << "Invalid owner when opening existing shared memory file."; - HANDLE_EINTR(close(fd)); + close(fd); return false; } // An existing file was opened, so its size should not be fixed. fix_size = false; } - fp = NULL; + + // Also open as readonly so that we can ShareReadOnlyToProcess. + *readonly_fd = HANDLE_EINTR(open(path.value().c_str(), O_RDONLY)); + if (*readonly_fd < 0) { + DPLOG(ERROR) << "open(\"" << path.value() << "\", O_RDONLY) failed"; + close(fd); + fd = -1; + } if (fd >= 0) { // "a+" is always appropriate: if it's a new file, a+ is similar to w+. - fp = fdopen(fd, "a+"); + fp.reset(fdopen(fd, "a+")); } } if (fp && fix_size) { // Get current size. struct stat stat; - if (fstat(fileno(fp), &stat) != 0) { - file_util::CloseFile(fp); + if (fstat(fileno(fp.get()), &stat) != 0) return false; - } const size_t current_size = stat.st_size; if (current_size != options.size) { - if (HANDLE_EINTR(ftruncate(fileno(fp), options.size)) != 0) { - file_util::CloseFile(fp); + if (HANDLE_EINTR(ftruncate(fileno(fp.get()), options.size)) != 0) return false; - } } requested_size_ = options.size; } @@ -225,7 +238,7 @@ bool SharedMemory::Create(const SharedMemoryCreateOptions& options) { return false; } - return PrepareMapFile(fp); + return PrepareMapFile(fp.Pass(), readonly_fd.Pass()); } // Our current implementation of shmem is with mmap()ing of files. @@ -251,8 +264,14 @@ bool SharedMemory::Open(const std::string& name, bool read_only) { read_only_ = read_only; const char *mode = read_only ? "r" : "r+"; - FILE *fp = file_util::OpenFile(path, mode); - return PrepareMapFile(fp); + ScopedFILE fp(base::OpenFile(path, mode)); + int readonly_fd_storage = -1; + ScopedFD readonly_fd(&readonly_fd_storage); + *readonly_fd = HANDLE_EINTR(open(path.value().c_str(), O_RDONLY)); + if (*readonly_fd < 0) { + DPLOG(ERROR) << "open(\"" << path.value() << "\", O_RDONLY) failed"; + } + return PrepareMapFile(fp.Pass(), readonly_fd.Pass()); } #endif // !defined(OS_ANDROID) @@ -309,10 +328,15 @@ void SharedMemory::Close() { Unmap(); if (mapped_file_ > 0) { - if (HANDLE_EINTR(close(mapped_file_)) < 0) + if (close(mapped_file_) < 0) PLOG(ERROR) << "close"; mapped_file_ = -1; } + if (readonly_mapped_file_ > 0) { + if (close(readonly_mapped_file_) < 0) + PLOG(ERROR) << "close"; + readonly_mapped_file_ = -1; + } } void SharedMemory::Lock() { @@ -326,18 +350,28 @@ void SharedMemory::Unlock() { } #if !defined(OS_ANDROID) -bool SharedMemory::PrepareMapFile(FILE *fp) { +bool SharedMemory::PrepareMapFile(ScopedFILE fp, ScopedFD readonly_fd) { DCHECK_EQ(-1, mapped_file_); - if (fp == NULL) return false; + DCHECK_EQ(-1, readonly_mapped_file_); + if (fp == NULL || *readonly_fd < 0) return false; // This function theoretically can block on the disk, but realistically // the temporary files we create will just go into the buffer cache // and be deleted before they ever make it out to disk. base::ThreadRestrictions::ScopedAllowIO allow_io; - file_util::ScopedFILE file_closer(fp); + struct stat st = {}; + struct stat readonly_st = {}; + if (fstat(fileno(fp.get()), &st)) + NOTREACHED(); + if (fstat(*readonly_fd, &readonly_st)) + NOTREACHED(); + if (st.st_dev != readonly_st.st_dev || st.st_ino != readonly_st.st_ino) { + LOG(ERROR) << "writable and read-only inodes don't match; bailing"; + return false; + } - mapped_file_ = dup(fileno(fp)); + mapped_file_ = dup(fileno(fp.get())); if (mapped_file_ == -1) { if (errno == EMFILE) { LOG(WARNING) << "Shared memory creation failed; out of file descriptors"; @@ -346,11 +380,8 @@ bool SharedMemory::PrepareMapFile(FILE *fp) { NOTREACHED() << "Call to dup failed, errno=" << errno; } } - - struct stat st; - if (fstat(mapped_file_, &st)) - NOTREACHED(); inode_ = st.st_ino; + readonly_mapped_file_ = *readonly_fd.release(); return true; } @@ -367,7 +398,7 @@ bool SharedMemory::FilePathForMemoryName(const std::string& mem_name, DCHECK_EQ(std::string::npos, mem_name.find('\0')); FilePath temp_dir; - if (!file_util::GetShmemTempDir(&temp_dir, false)) + if (!GetShmemTempDir(false, &temp_dir)) return false; #if !defined(OS_MACOSX) @@ -403,9 +434,23 @@ void SharedMemory::LockOrUnlockCommon(int function) { } bool SharedMemory::ShareToProcessCommon(ProcessHandle process, - SharedMemoryHandle *new_handle, - bool close_self) { - const int new_fd = dup(mapped_file_); + SharedMemoryHandle* new_handle, + bool close_self, + ShareMode share_mode) { + int handle_to_dup = -1; + switch(share_mode) { + case SHARE_CURRENT_MODE: + handle_to_dup = mapped_file_; + break; + case SHARE_READONLY: + // We could imagine re-opening the file from /dev/fd, but that can't make + // it readonly on Mac: https://codereview.chromium.org/27265002/#msg10 + CHECK(readonly_mapped_file_ >= 0); + handle_to_dup = readonly_mapped_file_; + break; + } + + const int new_fd = dup(handle_to_dup); if (new_fd < 0) { DPLOG(ERROR) << "dup() failed."; return false; diff --git a/chromium/base/memory/shared_memory_unittest.cc b/chromium/base/memory/shared_memory_unittest.cc index 892fd7f1a59..f3c612f803d 100644 --- a/chromium/base/memory/shared_memory_unittest.cc +++ b/chromium/base/memory/shared_memory_unittest.cc @@ -20,14 +20,22 @@ #endif #if defined(OS_POSIX) +#include <errno.h> +#include <fcntl.h> #include <sys/mman.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> #endif +#if defined(OS_WIN) +#include "base/win/scoped_handle.h" +#endif + static const int kNumThreads = 5; +#if !defined(OS_IOS) // iOS does not allow multiple processes. static const int kNumTasks = 5; +#endif namespace base { @@ -361,6 +369,107 @@ TEST(SharedMemoryTest, AnonymousPrivate) { } } +TEST(SharedMemoryTest, ShareReadOnly) { + StringPiece contents = "Hello World"; + + SharedMemory writable_shmem; + ASSERT_TRUE(writable_shmem.CreateAndMapAnonymous(contents.size())); + memcpy(writable_shmem.memory(), contents.data(), contents.size()); + EXPECT_TRUE(writable_shmem.Unmap()); + + SharedMemoryHandle readonly_handle; + ASSERT_TRUE(writable_shmem.ShareReadOnlyToProcess(GetCurrentProcessHandle(), + &readonly_handle)); + SharedMemory readonly_shmem(readonly_handle, /*readonly=*/true); + + ASSERT_TRUE(readonly_shmem.Map(contents.size())); + EXPECT_EQ(contents, + StringPiece(static_cast<const char*>(readonly_shmem.memory()), + contents.size())); + EXPECT_TRUE(readonly_shmem.Unmap()); + + // Make sure the writable instance is still writable. + ASSERT_TRUE(writable_shmem.Map(contents.size())); + StringPiece new_contents = "Goodbye"; + memcpy(writable_shmem.memory(), new_contents.data(), new_contents.size()); + EXPECT_EQ(new_contents, + StringPiece(static_cast<const char*>(writable_shmem.memory()), + new_contents.size())); + + // We'd like to check that if we send the read-only segment to another + // process, then that other process can't reopen it read/write. (Since that + // would be a security hole.) Setting up multiple processes is hard in a + // unittest, so this test checks that the *current* process can't reopen the + // segment read/write. I think the test here is stronger than we actually + // care about, but there's a remote possibility that sending a file over a + // pipe would transform it into read/write. + SharedMemoryHandle handle = readonly_shmem.handle(); + +#if defined(OS_ANDROID) + // The "read-only" handle is still writable on Android: + // http://crbug.com/320865 + (void)handle; +#elif defined(OS_POSIX) + EXPECT_EQ(O_RDONLY, fcntl(handle.fd, F_GETFL) & O_ACCMODE) + << "The descriptor itself should be read-only."; + + errno = 0; + void* writable = mmap( + NULL, contents.size(), PROT_READ | PROT_WRITE, MAP_SHARED, handle.fd, 0); + int mmap_errno = errno; + EXPECT_EQ(MAP_FAILED, writable) + << "It shouldn't be possible to re-mmap the descriptor writable."; + EXPECT_EQ(EACCES, mmap_errno) << strerror(mmap_errno); + if (writable != MAP_FAILED) + EXPECT_EQ(0, munmap(writable, readonly_shmem.mapped_size())); + +#elif defined(OS_WIN) + EXPECT_EQ(NULL, MapViewOfFile(handle, FILE_MAP_WRITE, 0, 0, 0)) + << "Shouldn't be able to map memory writable."; + + HANDLE temp_handle; + BOOL rv = ::DuplicateHandle(GetCurrentProcess(), + handle, + GetCurrentProcess, + &temp_handle, + FILE_MAP_ALL_ACCESS, + false, + 0); + EXPECT_EQ(FALSE, rv) + << "Shouldn't be able to duplicate the handle into a writable one."; + if (rv) + base::win::ScopedHandle writable_handle(temp_handle); +#else +#error Unexpected platform; write a test that tries to make 'handle' writable. +#endif // defined(OS_POSIX) || defined(OS_WIN) +} + +TEST(SharedMemoryTest, ShareToSelf) { + StringPiece contents = "Hello World"; + + SharedMemory shmem; + ASSERT_TRUE(shmem.CreateAndMapAnonymous(contents.size())); + memcpy(shmem.memory(), contents.data(), contents.size()); + EXPECT_TRUE(shmem.Unmap()); + + SharedMemoryHandle shared_handle; + ASSERT_TRUE(shmem.ShareToProcess(GetCurrentProcessHandle(), &shared_handle)); + SharedMemory shared(shared_handle, /*readonly=*/false); + + ASSERT_TRUE(shared.Map(contents.size())); + EXPECT_EQ( + contents, + StringPiece(static_cast<const char*>(shared.memory()), contents.size())); + + ASSERT_TRUE(shmem.ShareToProcess(GetCurrentProcessHandle(), &shared_handle)); + SharedMemory readonly(shared_handle, /*readonly=*/true); + + ASSERT_TRUE(readonly.Map(contents.size())); + EXPECT_EQ(contents, + StringPiece(static_cast<const char*>(readonly.memory()), + contents.size())); +} + TEST(SharedMemoryTest, MapAt) { ASSERT_TRUE(SysInfo::VMAllocationGranularity() >= sizeof(uint32)); const size_t kCount = SysInfo::VMAllocationGranularity(); diff --git a/chromium/base/memory/shared_memory_win.cc b/chromium/base/memory/shared_memory_win.cc index 42e0b046b96..77741bc0d9d 100644 --- a/chromium/base/memory/shared_memory_win.cc +++ b/chromium/base/memory/shared_memory_win.cc @@ -60,8 +60,8 @@ SharedMemory::SharedMemory(SharedMemoryHandle handle, bool read_only, lock_(NULL) { ::DuplicateHandle(process, handle, GetCurrentProcess(), &mapped_file_, - STANDARD_RIGHTS_REQUIRED | - (read_only_ ? FILE_MAP_READ : FILE_MAP_ALL_ACCESS), + read_only_ ? FILE_MAP_READ : FILE_MAP_READ | + FILE_MAP_WRITE, FALSE, 0); } @@ -147,8 +147,8 @@ bool SharedMemory::Open(const std::string& name, bool read_only) { name_ = ASCIIToWide(name); read_only_ = read_only; mapped_file_ = OpenFileMapping( - read_only_ ? FILE_MAP_READ : FILE_MAP_ALL_ACCESS, false, - name_.empty() ? NULL : name_.c_str()); + read_only_ ? FILE_MAP_READ : FILE_MAP_READ | FILE_MAP_WRITE, + false, name_.empty() ? NULL : name_.c_str()); if (mapped_file_ != NULL) { // Note: size_ is not set in this case. return true; @@ -164,7 +164,8 @@ bool SharedMemory::MapAt(off_t offset, size_t bytes) { return false; memory_ = MapViewOfFile(mapped_file_, - read_only_ ? FILE_MAP_READ : FILE_MAP_ALL_ACCESS, + read_only_ ? FILE_MAP_READ : FILE_MAP_READ | + FILE_MAP_WRITE, static_cast<uint64>(offset) >> 32, static_cast<DWORD>(offset), bytes); @@ -188,13 +189,14 @@ bool SharedMemory::Unmap() { bool SharedMemory::ShareToProcessCommon(ProcessHandle process, SharedMemoryHandle *new_handle, - bool close_self) { + bool close_self, + ShareMode share_mode) { *new_handle = 0; - DWORD access = STANDARD_RIGHTS_REQUIRED | FILE_MAP_READ; + DWORD access = FILE_MAP_READ; DWORD options = 0; HANDLE mapped_file = mapped_file_; HANDLE result; - if (!read_only_) + if (share_mode == SHARE_CURRENT_MODE && !read_only_) access |= FILE_MAP_WRITE; if (close_self) { // DUPLICATE_CLOSE_SOURCE causes DuplicateHandle to close mapped_file. diff --git a/chromium/base/message_loop/message_loop.cc b/chromium/base/message_loop/message_loop.cc index 87a0b631735..3f9d01c8620 100644 --- a/chromium/base/message_loop/message_loop.cc +++ b/chromium/base/message_loop/message_loop.cc @@ -144,58 +144,23 @@ MessageLoop::MessageLoop(Type type) #endif // OS_WIN message_histogram_(NULL), run_loop_(NULL) { - DCHECK(!current()) << "should only have one message loop per thread"; - lazy_tls_ptr.Pointer()->Set(this); + Init(); - incoming_task_queue_ = new internal::IncomingTaskQueue(this); - message_loop_proxy_ = - new internal::MessageLoopProxyImpl(incoming_task_queue_); - thread_task_runner_handle_.reset( - new ThreadTaskRunnerHandle(message_loop_proxy_)); + pump_.reset(CreateMessagePumpForType(type)); +} -// TODO(rvargas): Get rid of the OS guards. +MessageLoop::MessageLoop(scoped_ptr<MessagePump> pump) + : pump_(pump.Pass()), + type_(TYPE_CUSTOM), + exception_restoration_(false), + nestable_tasks_allowed_(true), #if defined(OS_WIN) -#define MESSAGE_PUMP_UI new MessagePumpForUI() -#define MESSAGE_PUMP_IO new MessagePumpForIO() -#elif defined(OS_IOS) -#define MESSAGE_PUMP_UI MessagePumpMac::Create() -#define MESSAGE_PUMP_IO new MessagePumpIOSForIO() -#elif defined(OS_MACOSX) -#define MESSAGE_PUMP_UI MessagePumpMac::Create() -#define MESSAGE_PUMP_IO new MessagePumpLibevent() -#elif defined(OS_NACL) -// Currently NaCl doesn't have a UI MessageLoop. -// TODO(abarth): Figure out if we need this. -#define MESSAGE_PUMP_UI NULL -// ipc_channel_nacl.cc uses a worker thread to do socket reads currently, and -// doesn't require extra support for watching file descriptors. -#define MESSAGE_PUMP_IO new MessagePumpDefault() -#elif defined(OS_POSIX) // POSIX but not MACOSX. -#define MESSAGE_PUMP_UI new MessagePumpForUI() -#define MESSAGE_PUMP_IO new MessagePumpLibevent() -#else -#error Not implemented -#endif - - if (type_ == TYPE_UI) { - if (message_pump_for_ui_factory_) - pump_.reset(message_pump_for_ui_factory_()); - else - pump_.reset(MESSAGE_PUMP_UI); - } else if (type_ == TYPE_IO) { - pump_.reset(MESSAGE_PUMP_IO); -#if defined(TOOLKIT_GTK) - } else if (type_ == TYPE_GPU) { - pump_.reset(new MessagePumpX11()); -#endif -#if defined(OS_ANDROID) - } else if (type_ == TYPE_JAVA) { - pump_.reset(MESSAGE_PUMP_UI); -#endif - } else { - DCHECK_EQ(TYPE_DEFAULT, type_); - pump_.reset(new MessagePumpDefault()); - } + os_modal_loop_(false), +#endif // OS_WIN + message_histogram_(NULL), + run_loop_(NULL) { + DCHECK(pump_.get()); + Init(); } MessageLoop::~MessageLoop() { @@ -257,6 +222,51 @@ bool MessageLoop::InitMessagePumpForUIFactory(MessagePumpFactory* factory) { return true; } +// static +MessagePump* MessageLoop::CreateMessagePumpForType(Type type) { +// TODO(rvargas): Get rid of the OS guards. +#if defined(OS_WIN) +#define MESSAGE_PUMP_UI new MessagePumpForUI() +#define MESSAGE_PUMP_IO new MessagePumpForIO() +#elif defined(OS_IOS) +#define MESSAGE_PUMP_UI MessagePumpMac::Create() +#define MESSAGE_PUMP_IO new MessagePumpIOSForIO() +#elif defined(OS_MACOSX) +#define MESSAGE_PUMP_UI MessagePumpMac::Create() +#define MESSAGE_PUMP_IO new MessagePumpLibevent() +#elif defined(OS_NACL) +// Currently NaCl doesn't have a UI MessageLoop. +// TODO(abarth): Figure out if we need this. +#define MESSAGE_PUMP_UI NULL +// ipc_channel_nacl.cc uses a worker thread to do socket reads currently, and +// doesn't require extra support for watching file descriptors. +#define MESSAGE_PUMP_IO new MessagePumpDefault() +#elif defined(OS_POSIX) // POSIX but not MACOSX. +#define MESSAGE_PUMP_UI new MessagePumpForUI() +#define MESSAGE_PUMP_IO new MessagePumpLibevent() +#else +#error Not implemented +#endif + + if (type == MessageLoop::TYPE_UI) { + if (message_pump_for_ui_factory_) + return message_pump_for_ui_factory_(); + return MESSAGE_PUMP_UI; + } + if (type == MessageLoop::TYPE_IO) + return MESSAGE_PUMP_IO; +#if defined(TOOLKIT_GTK) + if (type == MessageLoop::TYPE_GPU) + return new MessagePumpX11(); +#endif +#if defined(OS_ANDROID) + if (type == MessageLoop::TYPE_JAVA) + return MESSAGE_PUMP_UI; +#endif + DCHECK_EQ(MessageLoop::TYPE_DEFAULT, type); + return new MessagePumpDefault(); +} + void MessageLoop::AddDestructionObserver( DestructionObserver* destruction_observer) { DCHECK_EQ(this, current()); @@ -348,13 +358,12 @@ Closure MessageLoop::QuitWhenIdleClosure() { } void MessageLoop::SetNestableTasksAllowed(bool allowed) { - if (nestable_tasks_allowed_ != allowed) { - nestable_tasks_allowed_ = allowed; - if (!nestable_tasks_allowed_) - return; - // Start the native pump if we are not already pumping. + if (allowed) { + // Kick the native pump just in case we enter a OS-driven nested message + // loop. pump_->ScheduleWork(); } + nestable_tasks_allowed_ = allowed; } bool MessageLoop::NestableTasksAllowed() const { @@ -397,6 +406,17 @@ void MessageLoop::LockWaitUnLockForTesting(WaitableEvent* caller_wait, //------------------------------------------------------------------------------ +void MessageLoop::Init() { + DCHECK(!current()) << "should only have one message loop per thread"; + lazy_tls_ptr.Pointer()->Set(this); + + incoming_task_queue_ = new internal::IncomingTaskQueue(this); + message_loop_proxy_ = + new internal::MessageLoopProxyImpl(incoming_task_queue_); + thread_task_runner_handle_.reset( + new ThreadTaskRunnerHandle(message_loop_proxy_)); +} + // Runs the loop in two different SEH modes: // enable_SEH_restoration_ = false : any unhandled exception goes to the last // one that calls SetUnhandledExceptionFilter(). @@ -695,12 +715,6 @@ void MessageLoop::ReleaseSoonInternal( //------------------------------------------------------------------------------ // MessageLoopForUI -#if defined(OS_WIN) -void MessageLoopForUI::DidProcessMessage(const MSG& message) { - pump_win()->DidProcessMessage(message); -} -#endif // defined(OS_WIN) - #if defined(OS_ANDROID) void MessageLoopForUI::Start() { // No Histogram support for UI message loop as it is managed by Java side diff --git a/chromium/base/message_loop/message_loop.h b/chromium/base/message_loop/message_loop.h index 3520b72516f..e307d365a00 100644 --- a/chromium/base/message_loop/message_loop.h +++ b/chromium/base/message_loop/message_loop.h @@ -128,9 +128,13 @@ class BASE_EXPORT MessageLoop : public MessagePump::Delegate { // TYPE_JAVA behaves in essence like TYPE_UI, except during construction // where it does not use the main thread specific pump factory. // + // TYPE_CUSTOM + // MessagePump was supplied to constructor. + // enum Type { TYPE_DEFAULT, TYPE_UI, + TYPE_CUSTOM, #if defined(TOOLKIT_GTK) TYPE_GPU, #endif @@ -143,6 +147,9 @@ class BASE_EXPORT MessageLoop : public MessagePump::Delegate { // Normally, it is not necessary to instantiate a MessageLoop. Instead, it // is typical to make use of the current thread's MessageLoop instance. explicit MessageLoop(Type type = TYPE_DEFAULT); + // Creates a TYPE_CUSTOM MessageLoop with the supplied MessagePump, which must + // be non-NULL. + explicit MessageLoop(scoped_ptr<base::MessagePump> pump); virtual ~MessageLoop(); // Returns the MessageLoop object for the current thread, or null if none. @@ -156,6 +163,12 @@ class BASE_EXPORT MessageLoop : public MessagePump::Delegate { // was successfully registered. static bool InitMessagePumpForUIFactory(MessagePumpFactory* factory); + // Creates the default MessagePump based on |type|. Caller owns return + // value. + // TODO(sky): convert this and InitMessagePumpForUIFactory() to return a + // scoped_ptr. + static MessagePump* CreateMessagePumpForType(Type type); + // A DestructionObserver is notified when the current MessageLoop is being // destroyed. These observers are notified prior to MessageLoop::current() // being changed to return NULL. This gives interested parties the chance to @@ -442,6 +455,9 @@ class BASE_EXPORT MessageLoop : public MessagePump::Delegate { friend class internal::IncomingTaskQueue; friend class RunLoop; + // Configures various members for the two constructors. + void Init(); + // A function to encapsulate all the exception handling capability in the // stacks around the running of a main message loop. It will run the message // loop in a SEH try block or not depending on the set_SEH_restoration() @@ -504,7 +520,7 @@ class BASE_EXPORT MessageLoop : public MessagePump::Delegate { virtual void GetQueueingInformation(size_t* queue_size, TimeDelta* queueing_delay) OVERRIDE; - Type type_; + const Type type_; // A list of tasks that need to be processed by this instance. Note that // this queue is only accessed (push/pop) by our current thread. @@ -586,10 +602,6 @@ class BASE_EXPORT MessageLoopForUI : public MessageLoop { return static_cast<MessageLoopForUI*>(loop); } -#if defined(OS_WIN) - void DidProcessMessage(const MSG& message); -#endif // defined(OS_WIN) - #if defined(OS_IOS) // On iOS, the main message loop cannot be Run(). Instead call Attach(), // which connects this MessageLoop to the UI thread's CFRunLoop and allows diff --git a/chromium/base/message_loop/message_loop_test.cc b/chromium/base/message_loop/message_loop_test.cc new file mode 100644 index 00000000000..9ac5b72d374 --- /dev/null +++ b/chromium/base/message_loop/message_loop_test.cc @@ -0,0 +1,1069 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/message_loop/message_loop_test.h" + +#include "base/bind.h" +#include "base/memory/ref_counted.h" +#include "base/run_loop.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread.h" + +namespace base { +namespace test { + +namespace { + +class Foo : public RefCounted<Foo> { + public: + Foo() : test_count_(0) { + } + + void Test0() { + ++test_count_; + } + + void Test1ConstRef(const std::string& a) { + ++test_count_; + result_.append(a); + } + + void Test1Ptr(std::string* a) { + ++test_count_; + result_.append(*a); + } + + void Test1Int(int a) { + test_count_ += a; + } + + void Test2Ptr(std::string* a, std::string* b) { + ++test_count_; + result_.append(*a); + result_.append(*b); + } + + void Test2Mixed(const std::string& a, std::string* b) { + ++test_count_; + result_.append(a); + result_.append(*b); + } + + int test_count() const { return test_count_; } + const std::string& result() const { return result_; } + + private: + friend class RefCounted<Foo>; + + ~Foo() {} + + int test_count_; + std::string result_; + + DISALLOW_COPY_AND_ASSIGN(Foo); +}; + +// This function runs slowly to simulate a large amount of work being done. +void SlowFunc(TimeDelta pause, int* quit_counter) { + PlatformThread::Sleep(pause); + if (--(*quit_counter) == 0) + MessageLoop::current()->QuitWhenIdle(); +} + +// This function records the time when Run was called in a Time object, which is +// useful for building a variety of MessageLoop tests. +// TODO(sky): remove? +void RecordRunTimeFunc(Time* run_time, int* quit_counter) { + *run_time = Time::Now(); + + // Cause our Run function to take some time to execute. As a result we can + // count on subsequent RecordRunTimeFunc()s running at a future time, + // without worry about the resolution of our system clock being an issue. + SlowFunc(TimeDelta::FromMilliseconds(10), quit_counter); +} + +} // namespace + +void RunTest_PostTask(MessagePumpFactory factory) { + scoped_ptr<MessagePump> pump(factory()); + MessageLoop loop(pump.Pass()); + // Add tests to message loop + scoped_refptr<Foo> foo(new Foo()); + std::string a("a"), b("b"), c("c"), d("d"); + MessageLoop::current()->PostTask(FROM_HERE, Bind( + &Foo::Test0, foo.get())); + MessageLoop::current()->PostTask(FROM_HERE, Bind( + &Foo::Test1ConstRef, foo.get(), a)); + MessageLoop::current()->PostTask(FROM_HERE, Bind( + &Foo::Test1Ptr, foo.get(), &b)); + MessageLoop::current()->PostTask(FROM_HERE, Bind( + &Foo::Test1Int, foo.get(), 100)); + MessageLoop::current()->PostTask(FROM_HERE, Bind( + &Foo::Test2Ptr, foo.get(), &a, &c)); + + // TryPost with no contention. It must succeed. + EXPECT_TRUE(MessageLoop::current()->TryPostTask(FROM_HERE, Bind( + &Foo::Test2Mixed, foo.get(), a, &d))); + + // TryPost with simulated contention. It must fail. We wait for a helper + // thread to lock the queue, we TryPost on this thread and finally we + // signal the helper to unlock and exit. + WaitableEvent wait(true, false); + WaitableEvent signal(true, false); + Thread thread("RunTest_PostTask_helper"); + thread.Start(); + thread.message_loop()->PostTask( + FROM_HERE, + Bind(&MessageLoop::LockWaitUnLockForTesting, + base::Unretained(MessageLoop::current()), + &wait, + &signal)); + + wait.Wait(); + EXPECT_FALSE(MessageLoop::current()->TryPostTask(FROM_HERE, Bind( + &Foo::Test2Mixed, foo.get(), a, &d))); + signal.Signal(); + + // After all tests, post a message that will shut down the message loop + MessageLoop::current()->PostTask(FROM_HERE, Bind( + &MessageLoop::Quit, Unretained(MessageLoop::current()))); + + // Now kick things off + MessageLoop::current()->Run(); + + EXPECT_EQ(foo->test_count(), 105); + EXPECT_EQ(foo->result(), "abacad"); +} + +void RunTest_PostTask_SEH(MessagePumpFactory factory) { + scoped_ptr<MessagePump> pump(factory()); + MessageLoop loop(pump.Pass()); + + // Add tests to message loop + scoped_refptr<Foo> foo(new Foo()); + std::string a("a"), b("b"), c("c"), d("d"); + MessageLoop::current()->PostTask(FROM_HERE, Bind( + &Foo::Test0, foo.get())); + MessageLoop::current()->PostTask(FROM_HERE, Bind( + &Foo::Test1ConstRef, foo.get(), a)); + MessageLoop::current()->PostTask(FROM_HERE, Bind( + &Foo::Test1Ptr, foo.get(), &b)); + MessageLoop::current()->PostTask(FROM_HERE, Bind( + &Foo::Test1Int, foo.get(), 100)); + MessageLoop::current()->PostTask(FROM_HERE, Bind( + &Foo::Test2Ptr, foo.get(), &a, &c)); + MessageLoop::current()->PostTask(FROM_HERE, Bind( + &Foo::Test2Mixed, foo.get(), a, &d)); + + // After all tests, post a message that will shut down the message loop + MessageLoop::current()->PostTask(FROM_HERE, Bind( + &MessageLoop::Quit, Unretained(MessageLoop::current()))); + + // Now kick things off with the SEH block active. + MessageLoop::current()->set_exception_restoration(true); + MessageLoop::current()->Run(); + MessageLoop::current()->set_exception_restoration(false); + + EXPECT_EQ(foo->test_count(), 105); + EXPECT_EQ(foo->result(), "abacad"); +} + +void RunTest_PostDelayedTask_Basic(MessagePumpFactory factory) { + scoped_ptr<MessagePump> pump(factory()); + MessageLoop loop(pump.Pass()); + + // Test that PostDelayedTask results in a delayed task. + + const TimeDelta kDelay = TimeDelta::FromMilliseconds(100); + + int num_tasks = 1; + Time run_time; + + loop.PostDelayedTask( + FROM_HERE, Bind(&RecordRunTimeFunc, &run_time, &num_tasks), + kDelay); + + Time time_before_run = Time::Now(); + loop.Run(); + Time time_after_run = Time::Now(); + + EXPECT_EQ(0, num_tasks); + EXPECT_LT(kDelay, time_after_run - time_before_run); +} + +void RunTest_PostDelayedTask_InDelayOrder(MessagePumpFactory factory) { + scoped_ptr<MessagePump> pump(factory()); + MessageLoop loop(pump.Pass()); + + // Test that two tasks with different delays run in the right order. + int num_tasks = 2; + Time run_time1, run_time2; + + loop.PostDelayedTask( + FROM_HERE, + Bind(&RecordRunTimeFunc, &run_time1, &num_tasks), + TimeDelta::FromMilliseconds(200)); + // If we get a large pause in execution (due to a context switch) here, this + // test could fail. + loop.PostDelayedTask( + FROM_HERE, + Bind(&RecordRunTimeFunc, &run_time2, &num_tasks), + TimeDelta::FromMilliseconds(10)); + + loop.Run(); + EXPECT_EQ(0, num_tasks); + + EXPECT_TRUE(run_time2 < run_time1); +} + +void RunTest_PostDelayedTask_InPostOrder(MessagePumpFactory factory) { + scoped_ptr<MessagePump> pump(factory()); + MessageLoop loop(pump.Pass()); + + // Test that two tasks with the same delay run in the order in which they + // were posted. + // + // NOTE: This is actually an approximate test since the API only takes a + // "delay" parameter, so we are not exactly simulating two tasks that get + // posted at the exact same time. It would be nice if the API allowed us to + // specify the desired run time. + + const TimeDelta kDelay = TimeDelta::FromMilliseconds(100); + + int num_tasks = 2; + Time run_time1, run_time2; + + loop.PostDelayedTask( + FROM_HERE, + Bind(&RecordRunTimeFunc, &run_time1, &num_tasks), kDelay); + loop.PostDelayedTask( + FROM_HERE, + Bind(&RecordRunTimeFunc, &run_time2, &num_tasks), kDelay); + + loop.Run(); + EXPECT_EQ(0, num_tasks); + + EXPECT_TRUE(run_time1 < run_time2); +} + +void RunTest_PostDelayedTask_InPostOrder_2(MessagePumpFactory factory) { + scoped_ptr<MessagePump> pump(factory()); + MessageLoop loop(pump.Pass()); + + // Test that a delayed task still runs after a normal tasks even if the + // normal tasks take a long time to run. + + const TimeDelta kPause = TimeDelta::FromMilliseconds(50); + + int num_tasks = 2; + Time run_time; + + loop.PostTask(FROM_HERE, Bind(&SlowFunc, kPause, &num_tasks)); + loop.PostDelayedTask( + FROM_HERE, + Bind(&RecordRunTimeFunc, &run_time, &num_tasks), + TimeDelta::FromMilliseconds(10)); + + Time time_before_run = Time::Now(); + loop.Run(); + Time time_after_run = Time::Now(); + + EXPECT_EQ(0, num_tasks); + + EXPECT_LT(kPause, time_after_run - time_before_run); +} + +void RunTest_PostDelayedTask_InPostOrder_3(MessagePumpFactory factory) { + scoped_ptr<MessagePump> pump(factory()); + MessageLoop loop(pump.Pass()); + + // Test that a delayed task still runs after a pile of normal tasks. The key + // difference between this test and the previous one is that here we return + // the MessageLoop a lot so we give the MessageLoop plenty of opportunities + // to maybe run the delayed task. It should know not to do so until the + // delayed task's delay has passed. + + int num_tasks = 11; + Time run_time1, run_time2; + + // Clutter the ML with tasks. + for (int i = 1; i < num_tasks; ++i) + loop.PostTask(FROM_HERE, + Bind(&RecordRunTimeFunc, &run_time1, &num_tasks)); + + loop.PostDelayedTask( + FROM_HERE, Bind(&RecordRunTimeFunc, &run_time2, &num_tasks), + TimeDelta::FromMilliseconds(1)); + + loop.Run(); + EXPECT_EQ(0, num_tasks); + + EXPECT_TRUE(run_time2 > run_time1); +} + +void RunTest_PostDelayedTask_SharedTimer(MessagePumpFactory factory) { + scoped_ptr<MessagePump> pump(factory()); + MessageLoop loop(pump.Pass()); + + // Test that the interval of the timer, used to run the next delayed task, is + // set to a value corresponding to when the next delayed task should run. + + // By setting num_tasks to 1, we ensure that the first task to run causes the + // run loop to exit. + int num_tasks = 1; + Time run_time1, run_time2; + + loop.PostDelayedTask( + FROM_HERE, + Bind(&RecordRunTimeFunc, &run_time1, &num_tasks), + TimeDelta::FromSeconds(1000)); + loop.PostDelayedTask( + FROM_HERE, + Bind(&RecordRunTimeFunc, &run_time2, &num_tasks), + TimeDelta::FromMilliseconds(10)); + + Time start_time = Time::Now(); + + loop.Run(); + EXPECT_EQ(0, num_tasks); + + // Ensure that we ran in far less time than the slower timer. + TimeDelta total_time = Time::Now() - start_time; + EXPECT_GT(5000, total_time.InMilliseconds()); + + // In case both timers somehow run at nearly the same time, sleep a little + // and then run all pending to force them both to have run. This is just + // encouraging flakiness if there is any. + PlatformThread::Sleep(TimeDelta::FromMilliseconds(100)); + RunLoop().RunUntilIdle(); + + EXPECT_TRUE(run_time1.is_null()); + EXPECT_FALSE(run_time2.is_null()); +} + +// This is used to inject a test point for recording the destructor calls for +// Closure objects send to MessageLoop::PostTask(). It is awkward usage since we +// are trying to hook the actual destruction, which is not a common operation. +class RecordDeletionProbe : public RefCounted<RecordDeletionProbe> { + public: + RecordDeletionProbe(RecordDeletionProbe* post_on_delete, bool* was_deleted) + : post_on_delete_(post_on_delete), was_deleted_(was_deleted) { + } + void Run() {} + + private: + friend class RefCounted<RecordDeletionProbe>; + + ~RecordDeletionProbe() { + *was_deleted_ = true; + if (post_on_delete_.get()) + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&RecordDeletionProbe::Run, post_on_delete_.get())); + } + + scoped_refptr<RecordDeletionProbe> post_on_delete_; + bool* was_deleted_; +}; + +void RunTest_EnsureDeletion(MessagePumpFactory factory) { + bool a_was_deleted = false; + bool b_was_deleted = false; + { + scoped_ptr<MessagePump> pump(factory()); + MessageLoop loop(pump.Pass()); + loop.PostTask( + FROM_HERE, Bind(&RecordDeletionProbe::Run, + new RecordDeletionProbe(NULL, &a_was_deleted))); + // TODO(ajwong): Do we really need 1000ms here? + loop.PostDelayedTask( + FROM_HERE, Bind(&RecordDeletionProbe::Run, + new RecordDeletionProbe(NULL, &b_was_deleted)), + TimeDelta::FromMilliseconds(1000)); + } + EXPECT_TRUE(a_was_deleted); + EXPECT_TRUE(b_was_deleted); +} + +void RunTest_EnsureDeletion_Chain(MessagePumpFactory factory) { + bool a_was_deleted = false; + bool b_was_deleted = false; + bool c_was_deleted = false; + { + scoped_ptr<MessagePump> pump(factory()); + MessageLoop loop(pump.Pass()); + // The scoped_refptr for each of the below is held either by the chained + // RecordDeletionProbe, or the bound RecordDeletionProbe::Run() callback. + RecordDeletionProbe* a = new RecordDeletionProbe(NULL, &a_was_deleted); + RecordDeletionProbe* b = new RecordDeletionProbe(a, &b_was_deleted); + RecordDeletionProbe* c = new RecordDeletionProbe(b, &c_was_deleted); + loop.PostTask(FROM_HERE, Bind(&RecordDeletionProbe::Run, c)); + } + EXPECT_TRUE(a_was_deleted); + EXPECT_TRUE(b_was_deleted); + EXPECT_TRUE(c_was_deleted); +} + +void NestingFunc(int* depth) { + if (*depth > 0) { + *depth -= 1; + MessageLoop::current()->PostTask(FROM_HERE, + Bind(&NestingFunc, depth)); + + MessageLoop::current()->SetNestableTasksAllowed(true); + MessageLoop::current()->Run(); + } + MessageLoop::current()->QuitWhenIdle(); +} + +void RunTest_Nesting(MessagePumpFactory factory) { + scoped_ptr<MessagePump> pump(factory()); + MessageLoop loop(pump.Pass()); + + int depth = 100; + MessageLoop::current()->PostTask(FROM_HERE, + Bind(&NestingFunc, &depth)); + MessageLoop::current()->Run(); + EXPECT_EQ(depth, 0); +} + +enum TaskType { + MESSAGEBOX, + ENDDIALOG, + RECURSIVE, + TIMEDMESSAGELOOP, + QUITMESSAGELOOP, + ORDERED, + PUMPS, + SLEEP, + RUNS, +}; + +struct TaskItem { + TaskItem(TaskType t, int c, bool s) + : type(t), + cookie(c), + start(s) { + } + + TaskType type; + int cookie; + bool start; + + bool operator == (const TaskItem& other) const { + return type == other.type && cookie == other.cookie && start == other.start; + } +}; + +std::ostream& operator <<(std::ostream& os, TaskType type) { + switch (type) { + case MESSAGEBOX: os << "MESSAGEBOX"; break; + case ENDDIALOG: os << "ENDDIALOG"; break; + case RECURSIVE: os << "RECURSIVE"; break; + case TIMEDMESSAGELOOP: os << "TIMEDMESSAGELOOP"; break; + case QUITMESSAGELOOP: os << "QUITMESSAGELOOP"; break; + case ORDERED: os << "ORDERED"; break; + case PUMPS: os << "PUMPS"; break; + case SLEEP: os << "SLEEP"; break; + default: + NOTREACHED(); + os << "Unknown TaskType"; + break; + } + return os; +} + +std::ostream& operator <<(std::ostream& os, const TaskItem& item) { + if (item.start) + return os << item.type << " " << item.cookie << " starts"; + else + return os << item.type << " " << item.cookie << " ends"; +} + +class TaskList { + public: + void RecordStart(TaskType type, int cookie) { + TaskItem item(type, cookie, true); + DVLOG(1) << item; + task_list_.push_back(item); + } + + void RecordEnd(TaskType type, int cookie) { + TaskItem item(type, cookie, false); + DVLOG(1) << item; + task_list_.push_back(item); + } + + size_t Size() { + return task_list_.size(); + } + + TaskItem Get(int n) { + return task_list_[n]; + } + + private: + std::vector<TaskItem> task_list_; +}; + +void RecursiveFunc(TaskList* order, int cookie, int depth, + bool is_reentrant) { + order->RecordStart(RECURSIVE, cookie); + if (depth > 0) { + if (is_reentrant) + MessageLoop::current()->SetNestableTasksAllowed(true); + MessageLoop::current()->PostTask( + FROM_HERE, + Bind(&RecursiveFunc, order, cookie, depth - 1, is_reentrant)); + } + order->RecordEnd(RECURSIVE, cookie); +} + +void QuitFunc(TaskList* order, int cookie) { + order->RecordStart(QUITMESSAGELOOP, cookie); + MessageLoop::current()->QuitWhenIdle(); + order->RecordEnd(QUITMESSAGELOOP, cookie); +} +void RunTest_RecursiveDenial1(MessagePumpFactory factory) { + scoped_ptr<MessagePump> pump(factory()); + MessageLoop loop(pump.Pass()); + + EXPECT_TRUE(MessageLoop::current()->NestableTasksAllowed()); + TaskList order; + MessageLoop::current()->PostTask( + FROM_HERE, + Bind(&RecursiveFunc, &order, 1, 2, false)); + MessageLoop::current()->PostTask( + FROM_HERE, + Bind(&RecursiveFunc, &order, 2, 2, false)); + MessageLoop::current()->PostTask( + FROM_HERE, + Bind(&QuitFunc, &order, 3)); + + MessageLoop::current()->Run(); + + // FIFO order. + ASSERT_EQ(14U, order.Size()); + EXPECT_EQ(order.Get(0), TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order.Get(1), TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order.Get(2), TaskItem(RECURSIVE, 2, true)); + EXPECT_EQ(order.Get(3), TaskItem(RECURSIVE, 2, false)); + EXPECT_EQ(order.Get(4), TaskItem(QUITMESSAGELOOP, 3, true)); + EXPECT_EQ(order.Get(5), TaskItem(QUITMESSAGELOOP, 3, false)); + EXPECT_EQ(order.Get(6), TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order.Get(7), TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order.Get(8), TaskItem(RECURSIVE, 2, true)); + EXPECT_EQ(order.Get(9), TaskItem(RECURSIVE, 2, false)); + EXPECT_EQ(order.Get(10), TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order.Get(11), TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order.Get(12), TaskItem(RECURSIVE, 2, true)); + EXPECT_EQ(order.Get(13), TaskItem(RECURSIVE, 2, false)); +} + +void RecursiveSlowFunc(TaskList* order, int cookie, int depth, + bool is_reentrant) { + RecursiveFunc(order, cookie, depth, is_reentrant); + PlatformThread::Sleep(TimeDelta::FromMilliseconds(10)); +} + +void OrderedFunc(TaskList* order, int cookie) { + order->RecordStart(ORDERED, cookie); + order->RecordEnd(ORDERED, cookie); +} + +void RunTest_RecursiveDenial3(MessagePumpFactory factory) { + scoped_ptr<MessagePump> pump(factory()); + MessageLoop loop(pump.Pass()); + + EXPECT_TRUE(MessageLoop::current()->NestableTasksAllowed()); + TaskList order; + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&RecursiveSlowFunc, &order, 1, 2, false)); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&RecursiveSlowFunc, &order, 2, 2, false)); + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + Bind(&OrderedFunc, &order, 3), + TimeDelta::FromMilliseconds(5)); + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + Bind(&QuitFunc, &order, 4), + TimeDelta::FromMilliseconds(5)); + + MessageLoop::current()->Run(); + + // FIFO order. + ASSERT_EQ(16U, order.Size()); + EXPECT_EQ(order.Get(0), TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order.Get(1), TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order.Get(2), TaskItem(RECURSIVE, 2, true)); + EXPECT_EQ(order.Get(3), TaskItem(RECURSIVE, 2, false)); + EXPECT_EQ(order.Get(4), TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order.Get(5), TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order.Get(6), TaskItem(ORDERED, 3, true)); + EXPECT_EQ(order.Get(7), TaskItem(ORDERED, 3, false)); + EXPECT_EQ(order.Get(8), TaskItem(RECURSIVE, 2, true)); + EXPECT_EQ(order.Get(9), TaskItem(RECURSIVE, 2, false)); + EXPECT_EQ(order.Get(10), TaskItem(QUITMESSAGELOOP, 4, true)); + EXPECT_EQ(order.Get(11), TaskItem(QUITMESSAGELOOP, 4, false)); + EXPECT_EQ(order.Get(12), TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order.Get(13), TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order.Get(14), TaskItem(RECURSIVE, 2, true)); + EXPECT_EQ(order.Get(15), TaskItem(RECURSIVE, 2, false)); +} + +void RunTest_RecursiveSupport1(MessagePumpFactory factory) { + scoped_ptr<MessagePump> pump(factory()); + MessageLoop loop(pump.Pass()); + + TaskList order; + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&RecursiveFunc, &order, 1, 2, true)); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&RecursiveFunc, &order, 2, 2, true)); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&QuitFunc, &order, 3)); + + MessageLoop::current()->Run(); + + // FIFO order. + ASSERT_EQ(14U, order.Size()); + EXPECT_EQ(order.Get(0), TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order.Get(1), TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order.Get(2), TaskItem(RECURSIVE, 2, true)); + EXPECT_EQ(order.Get(3), TaskItem(RECURSIVE, 2, false)); + EXPECT_EQ(order.Get(4), TaskItem(QUITMESSAGELOOP, 3, true)); + EXPECT_EQ(order.Get(5), TaskItem(QUITMESSAGELOOP, 3, false)); + EXPECT_EQ(order.Get(6), TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order.Get(7), TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order.Get(8), TaskItem(RECURSIVE, 2, true)); + EXPECT_EQ(order.Get(9), TaskItem(RECURSIVE, 2, false)); + EXPECT_EQ(order.Get(10), TaskItem(RECURSIVE, 1, true)); + EXPECT_EQ(order.Get(11), TaskItem(RECURSIVE, 1, false)); + EXPECT_EQ(order.Get(12), TaskItem(RECURSIVE, 2, true)); + EXPECT_EQ(order.Get(13), TaskItem(RECURSIVE, 2, false)); +} + +// Tests that non nestable tasks run in FIFO if there are no nested loops. +void RunTest_NonNestableWithNoNesting(MessagePumpFactory factory) { + scoped_ptr<MessagePump> pump(factory()); + MessageLoop loop(pump.Pass()); + + TaskList order; + + MessageLoop::current()->PostNonNestableTask( + FROM_HERE, + Bind(&OrderedFunc, &order, 1)); + MessageLoop::current()->PostTask(FROM_HERE, + Bind(&OrderedFunc, &order, 2)); + MessageLoop::current()->PostTask(FROM_HERE, + Bind(&QuitFunc, &order, 3)); + MessageLoop::current()->Run(); + + // FIFO order. + ASSERT_EQ(6U, order.Size()); + EXPECT_EQ(order.Get(0), TaskItem(ORDERED, 1, true)); + EXPECT_EQ(order.Get(1), TaskItem(ORDERED, 1, false)); + EXPECT_EQ(order.Get(2), TaskItem(ORDERED, 2, true)); + EXPECT_EQ(order.Get(3), TaskItem(ORDERED, 2, false)); + EXPECT_EQ(order.Get(4), TaskItem(QUITMESSAGELOOP, 3, true)); + EXPECT_EQ(order.Get(5), TaskItem(QUITMESSAGELOOP, 3, false)); +} + +void FuncThatPumps(TaskList* order, int cookie) { + order->RecordStart(PUMPS, cookie); + { + MessageLoop::ScopedNestableTaskAllower allow(MessageLoop::current()); + RunLoop().RunUntilIdle(); + } + order->RecordEnd(PUMPS, cookie); +} + +void SleepFunc(TaskList* order, int cookie, TimeDelta delay) { + order->RecordStart(SLEEP, cookie); + PlatformThread::Sleep(delay); + order->RecordEnd(SLEEP, cookie); +} + +// Tests that non nestable tasks don't run when there's code in the call stack. +void RunTest_NonNestableInNestedLoop(MessagePumpFactory factory, + bool use_delayed) { + scoped_ptr<MessagePump> pump(factory()); + MessageLoop loop(pump.Pass()); + + TaskList order; + + MessageLoop::current()->PostTask( + FROM_HERE, + Bind(&FuncThatPumps, &order, 1)); + if (use_delayed) { + MessageLoop::current()->PostNonNestableDelayedTask( + FROM_HERE, + Bind(&OrderedFunc, &order, 2), + TimeDelta::FromMilliseconds(1)); + } else { + MessageLoop::current()->PostNonNestableTask( + FROM_HERE, + Bind(&OrderedFunc, &order, 2)); + } + MessageLoop::current()->PostTask(FROM_HERE, + Bind(&OrderedFunc, &order, 3)); + MessageLoop::current()->PostTask( + FROM_HERE, + Bind(&SleepFunc, &order, 4, TimeDelta::FromMilliseconds(50))); + MessageLoop::current()->PostTask(FROM_HERE, + Bind(&OrderedFunc, &order, 5)); + if (use_delayed) { + MessageLoop::current()->PostNonNestableDelayedTask( + FROM_HERE, + Bind(&QuitFunc, &order, 6), + TimeDelta::FromMilliseconds(2)); + } else { + MessageLoop::current()->PostNonNestableTask( + FROM_HERE, + Bind(&QuitFunc, &order, 6)); + } + + MessageLoop::current()->Run(); + + // FIFO order. + ASSERT_EQ(12U, order.Size()); + EXPECT_EQ(order.Get(0), TaskItem(PUMPS, 1, true)); + EXPECT_EQ(order.Get(1), TaskItem(ORDERED, 3, true)); + EXPECT_EQ(order.Get(2), TaskItem(ORDERED, 3, false)); + EXPECT_EQ(order.Get(3), TaskItem(SLEEP, 4, true)); + EXPECT_EQ(order.Get(4), TaskItem(SLEEP, 4, false)); + EXPECT_EQ(order.Get(5), TaskItem(ORDERED, 5, true)); + EXPECT_EQ(order.Get(6), TaskItem(ORDERED, 5, false)); + EXPECT_EQ(order.Get(7), TaskItem(PUMPS, 1, false)); + EXPECT_EQ(order.Get(8), TaskItem(ORDERED, 2, true)); + EXPECT_EQ(order.Get(9), TaskItem(ORDERED, 2, false)); + EXPECT_EQ(order.Get(10), TaskItem(QUITMESSAGELOOP, 6, true)); + EXPECT_EQ(order.Get(11), TaskItem(QUITMESSAGELOOP, 6, false)); +} + +void FuncThatRuns(TaskList* order, int cookie, RunLoop* run_loop) { + order->RecordStart(RUNS, cookie); + { + MessageLoop::ScopedNestableTaskAllower allow(MessageLoop::current()); + run_loop->Run(); + } + order->RecordEnd(RUNS, cookie); +} + +void FuncThatQuitsNow() { + MessageLoop::current()->QuitNow(); +} +// Tests RunLoopQuit only quits the corresponding MessageLoop::Run. +void RunTest_QuitNow(MessagePumpFactory factory) { + scoped_ptr<MessagePump> pump(factory()); + MessageLoop loop(pump.Pass()); + + TaskList order; + + RunLoop run_loop; + + MessageLoop::current()->PostTask(FROM_HERE, + Bind(&FuncThatRuns, &order, 1, Unretained(&run_loop))); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&OrderedFunc, &order, 2)); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&FuncThatQuitsNow)); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&OrderedFunc, &order, 3)); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&FuncThatQuitsNow)); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&OrderedFunc, &order, 4)); // never runs + + MessageLoop::current()->Run(); + + ASSERT_EQ(6U, order.Size()); + int task_index = 0; + EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, false)); + EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, false)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 3, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 3, false)); + EXPECT_EQ(static_cast<size_t>(task_index), order.Size()); +} + +// Tests RunLoopQuit only quits the corresponding MessageLoop::Run. +void RunTest_RunLoopQuitTop(MessagePumpFactory factory) { + scoped_ptr<MessagePump> pump(factory()); + MessageLoop loop(pump.Pass()); + + TaskList order; + + RunLoop outer_run_loop; + RunLoop nested_run_loop; + + MessageLoop::current()->PostTask(FROM_HERE, + Bind(&FuncThatRuns, &order, 1, Unretained(&nested_run_loop))); + MessageLoop::current()->PostTask( + FROM_HERE, outer_run_loop.QuitClosure()); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&OrderedFunc, &order, 2)); + MessageLoop::current()->PostTask( + FROM_HERE, nested_run_loop.QuitClosure()); + + outer_run_loop.Run(); + + ASSERT_EQ(4U, order.Size()); + int task_index = 0; + EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, false)); + EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, false)); + EXPECT_EQ(static_cast<size_t>(task_index), order.Size()); +} + +// Tests RunLoopQuit only quits the corresponding MessageLoop::Run. +void RunTest_RunLoopQuitNested(MessagePumpFactory factory) { + scoped_ptr<MessagePump> pump(factory()); + MessageLoop loop(pump.Pass()); + + TaskList order; + + RunLoop outer_run_loop; + RunLoop nested_run_loop; + + MessageLoop::current()->PostTask(FROM_HERE, + Bind(&FuncThatRuns, &order, 1, Unretained(&nested_run_loop))); + MessageLoop::current()->PostTask( + FROM_HERE, nested_run_loop.QuitClosure()); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&OrderedFunc, &order, 2)); + MessageLoop::current()->PostTask( + FROM_HERE, outer_run_loop.QuitClosure()); + + outer_run_loop.Run(); + + ASSERT_EQ(4U, order.Size()); + int task_index = 0; + EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, false)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, false)); + EXPECT_EQ(static_cast<size_t>(task_index), order.Size()); +} + +// Tests RunLoopQuit only quits the corresponding MessageLoop::Run. +void RunTest_RunLoopQuitBogus(MessagePumpFactory factory) { + scoped_ptr<MessagePump> pump(factory()); + MessageLoop loop(pump.Pass()); + + TaskList order; + + RunLoop outer_run_loop; + RunLoop nested_run_loop; + RunLoop bogus_run_loop; + + MessageLoop::current()->PostTask(FROM_HERE, + Bind(&FuncThatRuns, &order, 1, Unretained(&nested_run_loop))); + MessageLoop::current()->PostTask( + FROM_HERE, bogus_run_loop.QuitClosure()); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&OrderedFunc, &order, 2)); + MessageLoop::current()->PostTask( + FROM_HERE, outer_run_loop.QuitClosure()); + MessageLoop::current()->PostTask( + FROM_HERE, nested_run_loop.QuitClosure()); + + outer_run_loop.Run(); + + ASSERT_EQ(4U, order.Size()); + int task_index = 0; + EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, false)); + EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, false)); + EXPECT_EQ(static_cast<size_t>(task_index), order.Size()); +} + +// Tests RunLoopQuit only quits the corresponding MessageLoop::Run. +void RunTest_RunLoopQuitDeep(MessagePumpFactory factory) { + scoped_ptr<MessagePump> pump(factory()); + MessageLoop loop(pump.Pass()); + + TaskList order; + + RunLoop outer_run_loop; + RunLoop nested_loop1; + RunLoop nested_loop2; + RunLoop nested_loop3; + RunLoop nested_loop4; + + MessageLoop::current()->PostTask(FROM_HERE, + Bind(&FuncThatRuns, &order, 1, Unretained(&nested_loop1))); + MessageLoop::current()->PostTask(FROM_HERE, + Bind(&FuncThatRuns, &order, 2, Unretained(&nested_loop2))); + MessageLoop::current()->PostTask(FROM_HERE, + Bind(&FuncThatRuns, &order, 3, Unretained(&nested_loop3))); + MessageLoop::current()->PostTask(FROM_HERE, + Bind(&FuncThatRuns, &order, 4, Unretained(&nested_loop4))); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&OrderedFunc, &order, 5)); + MessageLoop::current()->PostTask( + FROM_HERE, outer_run_loop.QuitClosure()); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&OrderedFunc, &order, 6)); + MessageLoop::current()->PostTask( + FROM_HERE, nested_loop1.QuitClosure()); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&OrderedFunc, &order, 7)); + MessageLoop::current()->PostTask( + FROM_HERE, nested_loop2.QuitClosure()); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&OrderedFunc, &order, 8)); + MessageLoop::current()->PostTask( + FROM_HERE, nested_loop3.QuitClosure()); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&OrderedFunc, &order, 9)); + MessageLoop::current()->PostTask( + FROM_HERE, nested_loop4.QuitClosure()); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&OrderedFunc, &order, 10)); + + outer_run_loop.Run(); + + ASSERT_EQ(18U, order.Size()); + int task_index = 0; + EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 2, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 3, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 4, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 5, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 5, false)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 6, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 6, false)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 7, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 7, false)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 8, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 8, false)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 9, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 9, false)); + EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 4, false)); + EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 3, false)); + EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 2, false)); + EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, false)); + EXPECT_EQ(static_cast<size_t>(task_index), order.Size()); +} + +// Tests RunLoopQuit works before RunWithID. +void RunTest_RunLoopQuitOrderBefore(MessagePumpFactory factory) { + scoped_ptr<MessagePump> pump(factory()); + MessageLoop loop(pump.Pass()); + + TaskList order; + + RunLoop run_loop; + + run_loop.Quit(); + + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&OrderedFunc, &order, 1)); // never runs + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&FuncThatQuitsNow)); // never runs + + run_loop.Run(); + + ASSERT_EQ(0U, order.Size()); +} + +// Tests RunLoopQuit works during RunWithID. +void RunTest_RunLoopQuitOrderDuring(MessagePumpFactory factory) { + scoped_ptr<MessagePump> pump(factory()); + MessageLoop loop(pump.Pass()); + + TaskList order; + + RunLoop run_loop; + + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&OrderedFunc, &order, 1)); + MessageLoop::current()->PostTask( + FROM_HERE, run_loop.QuitClosure()); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&OrderedFunc, &order, 2)); // never runs + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&FuncThatQuitsNow)); // never runs + + run_loop.Run(); + + ASSERT_EQ(2U, order.Size()); + int task_index = 0; + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 1, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 1, false)); + EXPECT_EQ(static_cast<size_t>(task_index), order.Size()); +} + +// Tests RunLoopQuit works after RunWithID. +void RunTest_RunLoopQuitOrderAfter(MessagePumpFactory factory) { + scoped_ptr<MessagePump> pump(factory()); + MessageLoop loop(pump.Pass()); + + TaskList order; + + RunLoop run_loop; + + MessageLoop::current()->PostTask(FROM_HERE, + Bind(&FuncThatRuns, &order, 1, Unretained(&run_loop))); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&OrderedFunc, &order, 2)); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&FuncThatQuitsNow)); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&OrderedFunc, &order, 3)); + MessageLoop::current()->PostTask( + FROM_HERE, run_loop.QuitClosure()); // has no affect + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&OrderedFunc, &order, 4)); + MessageLoop::current()->PostTask( + FROM_HERE, Bind(&FuncThatQuitsNow)); + + RunLoop outer_run_loop; + outer_run_loop.Run(); + + ASSERT_EQ(8U, order.Size()); + int task_index = 0; + EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, false)); + EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, false)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 3, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 3, false)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 4, true)); + EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 4, false)); + EXPECT_EQ(static_cast<size_t>(task_index), order.Size()); +} + +void PostNTasksThenQuit(int posts_remaining) { + if (posts_remaining > 1) { + MessageLoop::current()->PostTask( + FROM_HERE, + Bind(&PostNTasksThenQuit, posts_remaining - 1)); + } else { + MessageLoop::current()->QuitWhenIdle(); + } +} + +// There was a bug in the MessagePumpGLib where posting tasks recursively +// caused the message loop to hang, due to the buffer of the internal pipe +// becoming full. Test all MessageLoop types to ensure this issue does not +// exist in other MessagePumps. +// +// On Linux, the pipe buffer size is 64KiB by default. The bug caused one +// byte accumulated in the pipe per two posts, so we should repeat 128K +// times to reproduce the bug. +void RunTest_RecursivePosts(MessagePumpFactory factory) { + const int kNumTimes = 1 << 17; + scoped_ptr<MessagePump> pump(factory()); + MessageLoop loop(pump.Pass()); + loop.PostTask(FROM_HERE, Bind(&PostNTasksThenQuit, kNumTimes)); + loop.Run(); +} + +} // namespace test +} // namespace base diff --git a/chromium/base/message_loop/message_loop_test.h b/chromium/base/message_loop/message_loop_test.h new file mode 100644 index 00000000000..5d1a4f5ac27 --- /dev/null +++ b/chromium/base/message_loop/message_loop_test.h @@ -0,0 +1,137 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MESSAGE_LOOP_MESSAGE_LOOP_TEST_H_ +#define BASE_MESSAGE_LOOP_MESSAGE_LOOP_TEST_H_ + +#include "base/message_loop/message_loop.h" +#include "testing/gtest/include/gtest/gtest.h" + +// This file consists of tests meant to exercise the combination of MessageLoop +// and MessagePump. To use these define the macro RUN_MESSAGE_LOOP_TESTS using +// an ID appropriate for your MessagePump, eg +// RUN_MESSAGE_LOOP_TESTS(UI, factory). Factory is a function called to create +// the MessagePump. +namespace base { +namespace test { + +typedef MessageLoop::MessagePumpFactory MessagePumpFactory; + +void RunTest_PostTask(MessagePumpFactory factory); +void RunTest_PostTask_SEH(MessagePumpFactory factory); +void RunTest_PostDelayedTask_Basic(MessagePumpFactory factory); +void RunTest_PostDelayedTask_InDelayOrder(MessagePumpFactory factory); +void RunTest_PostDelayedTask_InPostOrder(MessagePumpFactory factory); +void RunTest_PostDelayedTask_InPostOrder_2(MessagePumpFactory factory); +void RunTest_PostDelayedTask_InPostOrder_3(MessagePumpFactory factory); +void RunTest_PostDelayedTask_SharedTimer(MessagePumpFactory factory); +void RunTest_EnsureDeletion(MessagePumpFactory factory); +void RunTest_EnsureDeletion_Chain(MessagePumpFactory factory); +void RunTest_Nesting(MessagePumpFactory factory); +void RunTest_RecursiveDenial1(MessagePumpFactory factory); +void RunTest_RecursiveDenial3(MessagePumpFactory factory); +void RunTest_RecursiveSupport1(MessagePumpFactory factory); +void RunTest_NonNestableWithNoNesting(MessagePumpFactory factory); +void RunTest_NonNestableInNestedLoop(MessagePumpFactory factory, + bool use_delayed); +void RunTest_QuitNow(MessagePumpFactory factory); +void RunTest_RunLoopQuitTop(MessagePumpFactory factory); +void RunTest_RunLoopQuitNested(MessagePumpFactory factory); +void RunTest_RunLoopQuitBogus(MessagePumpFactory factory); +void RunTest_RunLoopQuitDeep(MessagePumpFactory factory); +void RunTest_RunLoopQuitOrderBefore(MessagePumpFactory factory); +void RunTest_RunLoopQuitOrderDuring(MessagePumpFactory factory); +void RunTest_RunLoopQuitOrderAfter(MessagePumpFactory factory); +void RunTest_RecursivePosts(MessagePumpFactory factory); + +} // namespace test +} // namespace base + +#define RUN_MESSAGE_LOOP_TESTS(id, factory) \ + TEST(MessageLoopTestType##id, PostTask) { \ + base::test::RunTest_PostTask(factory); \ + } \ + TEST(MessageLoopTestType##id, PostTask_SEH) { \ + base::test::RunTest_PostTask_SEH(factory); \ + } \ + TEST(MessageLoopTestType##id, PostDelayedTask_Basic) { \ + base::test::RunTest_PostDelayedTask_Basic(factory); \ + } \ + TEST(MessageLoopTestType##id, PostDelayedTask_InDelayOrder) { \ + base::test::RunTest_PostDelayedTask_InDelayOrder(factory); \ + } \ + TEST(MessageLoopTestType##id, PostDelayedTask_InPostOrder) { \ + base::test::RunTest_PostDelayedTask_InPostOrder(factory); \ + } \ + TEST(MessageLoopTestType##id, PostDelayedTask_InPostOrder_2) { \ + base::test::RunTest_PostDelayedTask_InPostOrder_2(factory); \ + } \ + TEST(MessageLoopTestType##id, PostDelayedTask_InPostOrder_3) { \ + base::test::RunTest_PostDelayedTask_InPostOrder_3(factory); \ + } \ + TEST(MessageLoopTestType##id, PostDelayedTask_SharedTimer) { \ + base::test::RunTest_PostDelayedTask_SharedTimer(factory); \ + } \ + /* TODO(darin): MessageLoop does not support deleting all tasks in the */ \ + /* destructor. */ \ + /* Fails, http://crbug.com/50272. */ \ + TEST(MessageLoopTestType##id, DISABLED_EnsureDeletion) { \ + base::test::RunTest_EnsureDeletion(factory); \ + } \ + /* TODO(darin): MessageLoop does not support deleting all tasks in the */ \ + /* destructor. */ \ + /* Fails, http://crbug.com/50272. */ \ + TEST(MessageLoopTestType##id, DISABLED_EnsureDeletion_Chain) { \ + base::test::RunTest_EnsureDeletion_Chain(factory); \ + } \ + TEST(MessageLoopTestType##id, Nesting) { \ + base::test::RunTest_Nesting(factory); \ + } \ + TEST(MessageLoopTestType##id, RecursiveDenial1) { \ + base::test::RunTest_RecursiveDenial1(factory); \ + } \ + TEST(MessageLoopTestType##id, RecursiveDenial3) { \ + base::test::RunTest_RecursiveDenial3(factory); \ + } \ + TEST(MessageLoopTestType##id, RecursiveSupport1) { \ + base::test::RunTest_RecursiveSupport1(factory); \ + } \ + TEST(MessageLoopTestType##id, NonNestableWithNoNesting) { \ + base::test::RunTest_NonNestableWithNoNesting(factory); \ + } \ + TEST(MessageLoopTestType##id, NonNestableInNestedLoop) { \ + base::test::RunTest_NonNestableInNestedLoop(factory, false); \ + } \ + TEST(MessageLoopTestType##id, NonNestableDelayedInNestedLoop) { \ + base::test::RunTest_NonNestableInNestedLoop(factory, true); \ + } \ + TEST(MessageLoopTestType##id, QuitNow) { \ + base::test::RunTest_QuitNow(factory); \ + } \ + TEST(MessageLoopTestType##id, RunLoopQuitTop) { \ + base::test::RunTest_RunLoopQuitTop(factory); \ + } \ + TEST(MessageLoopTestType##id, RunLoopQuitNested) { \ + base::test::RunTest_RunLoopQuitNested(factory); \ + } \ + TEST(MessageLoopTestType##id, RunLoopQuitBogus) { \ + base::test::RunTest_RunLoopQuitBogus(factory); \ + } \ + TEST(MessageLoopTestType##id, RunLoopQuitDeep) { \ + base::test::RunTest_RunLoopQuitDeep(factory); \ + } \ + TEST(MessageLoopTestType##id, RunLoopQuitOrderBefore) { \ + base::test::RunTest_RunLoopQuitOrderBefore(factory); \ + } \ + TEST(MessageLoopTestType##id, RunLoopQuitOrderDuring) { \ + base::test::RunTest_RunLoopQuitOrderDuring(factory); \ + } \ + TEST(MessageLoopTestType##id, RunLoopQuitOrderAfter) { \ + base::test::RunTest_RunLoopQuitOrderAfter(factory); \ + } \ + TEST(MessageLoopTestType##id, RecursivePosts) { \ + base::test::RunTest_RecursivePosts(factory); \ + } \ + +#endif // BASE_MESSAGE_LOOP_MESSAGE_LOOP_TEST_H_ diff --git a/chromium/base/message_loop/message_loop_unittest.cc b/chromium/base/message_loop/message_loop_unittest.cc index ab05b3cb5ab..e6d25ecef82 100644 --- a/chromium/base/message_loop/message_loop_unittest.cc +++ b/chromium/base/message_loop/message_loop_unittest.cc @@ -11,6 +11,7 @@ #include "base/memory/ref_counted.h" #include "base/message_loop/message_loop.h" #include "base/message_loop/message_loop_proxy_impl.h" +#include "base/message_loop/message_loop_test.h" #include "base/pending_task.h" #include "base/posix/eintr_wrapper.h" #include "base/run_loop.h" @@ -22,6 +23,8 @@ #if defined(OS_WIN) #include "base/message_loop/message_pump_win.h" +#include "base/process/memory.h" +#include "base/strings/string16.h" #include "base/win/scoped_handle.h" #endif @@ -32,6 +35,18 @@ namespace base { namespace { +MessagePump* TypeDefaultMessagePumpFactory() { + return MessageLoop::CreateMessagePumpForType(MessageLoop::TYPE_DEFAULT); +} + +MessagePump* TypeIOMessagePumpFactory() { + return MessageLoop::CreateMessagePumpForType(MessageLoop::TYPE_IO); +} + +MessagePump* TypeUIMessagePumpFactory() { + return MessageLoop::CreateMessagePumpForType(MessageLoop::TYPE_UI); +} + class Foo : public RefCounted<Foo> { public: Foo() : test_count_(0) { @@ -79,88 +94,7 @@ class Foo : public RefCounted<Foo> { std::string result_; }; -void RunTest_PostTask(MessageLoop::Type message_loop_type) { - MessageLoop loop(message_loop_type); - - // Add tests to message loop - scoped_refptr<Foo> foo(new Foo()); - std::string a("a"), b("b"), c("c"), d("d"); - MessageLoop::current()->PostTask(FROM_HERE, Bind( - &Foo::Test0, foo.get())); - MessageLoop::current()->PostTask(FROM_HERE, Bind( - &Foo::Test1ConstRef, foo.get(), a)); - MessageLoop::current()->PostTask(FROM_HERE, Bind( - &Foo::Test1Ptr, foo.get(), &b)); - MessageLoop::current()->PostTask(FROM_HERE, Bind( - &Foo::Test1Int, foo.get(), 100)); - MessageLoop::current()->PostTask(FROM_HERE, Bind( - &Foo::Test2Ptr, foo.get(), &a, &c)); - - // TryPost with no contention. It must succeed. - EXPECT_TRUE(MessageLoop::current()->TryPostTask(FROM_HERE, Bind( - &Foo::Test2Mixed, foo.get(), a, &d))); - - // TryPost with simulated contention. It must fail. We wait for a helper - // thread to lock the queue, we TryPost on this thread and finally we - // signal the helper to unlock and exit. - WaitableEvent wait(true, false); - WaitableEvent signal(true, false); - Thread thread("RunTest_PostTask_helper"); - thread.Start(); - thread.message_loop()->PostTask( - FROM_HERE, - Bind(&MessageLoop::LockWaitUnLockForTesting, - base::Unretained(MessageLoop::current()), - &wait, - &signal)); - - wait.Wait(); - EXPECT_FALSE(MessageLoop::current()->TryPostTask(FROM_HERE, Bind( - &Foo::Test2Mixed, foo.get(), a, &d))); - signal.Signal(); - - // After all tests, post a message that will shut down the message loop - MessageLoop::current()->PostTask(FROM_HERE, Bind( - &MessageLoop::Quit, Unretained(MessageLoop::current()))); - - // Now kick things off - MessageLoop::current()->Run(); - - EXPECT_EQ(foo->test_count(), 105); - EXPECT_EQ(foo->result(), "abacad"); -} - -void RunTest_PostTask_SEH(MessageLoop::Type message_loop_type) { - MessageLoop loop(message_loop_type); - - // Add tests to message loop - scoped_refptr<Foo> foo(new Foo()); - std::string a("a"), b("b"), c("c"), d("d"); - MessageLoop::current()->PostTask(FROM_HERE, Bind( - &Foo::Test0, foo.get())); - MessageLoop::current()->PostTask(FROM_HERE, Bind( - &Foo::Test1ConstRef, foo.get(), a)); - MessageLoop::current()->PostTask(FROM_HERE, Bind( - &Foo::Test1Ptr, foo.get(), &b)); - MessageLoop::current()->PostTask(FROM_HERE, Bind( - &Foo::Test1Int, foo.get(), 100)); - MessageLoop::current()->PostTask(FROM_HERE, Bind( - &Foo::Test2Ptr, foo.get(), &a, &c)); - MessageLoop::current()->PostTask(FROM_HERE, Bind( - &Foo::Test2Mixed, foo.get(), a, &d)); - - // After all tests, post a message that will shut down the message loop - MessageLoop::current()->PostTask(FROM_HERE, Bind( - &MessageLoop::Quit, Unretained(MessageLoop::current()))); - - // Now kick things off with the SEH block active. - MessageLoop::current()->set_exception_restoration(true); - MessageLoop::current()->Run(); - MessageLoop::current()->set_exception_restoration(false); - - EXPECT_EQ(foo->test_count(), 105); - EXPECT_EQ(foo->result(), "abacad"); -} +#if defined(OS_WIN) // This function runs slowly to simulate a large amount of work being done. static void SlowFunc(TimeDelta pause, int* quit_counter) { @@ -180,180 +114,6 @@ static void RecordRunTimeFunc(Time* run_time, int* quit_counter) { SlowFunc(TimeDelta::FromMilliseconds(10), quit_counter); } -void RunTest_PostDelayedTask_Basic(MessageLoop::Type message_loop_type) { - MessageLoop loop(message_loop_type); - - // Test that PostDelayedTask results in a delayed task. - - const TimeDelta kDelay = TimeDelta::FromMilliseconds(100); - - int num_tasks = 1; - Time run_time; - - loop.PostDelayedTask( - FROM_HERE, Bind(&RecordRunTimeFunc, &run_time, &num_tasks), - kDelay); - - Time time_before_run = Time::Now(); - loop.Run(); - Time time_after_run = Time::Now(); - - EXPECT_EQ(0, num_tasks); - EXPECT_LT(kDelay, time_after_run - time_before_run); -} - -void RunTest_PostDelayedTask_InDelayOrder( - MessageLoop::Type message_loop_type) { - MessageLoop loop(message_loop_type); - - // Test that two tasks with different delays run in the right order. - int num_tasks = 2; - Time run_time1, run_time2; - - loop.PostDelayedTask( - FROM_HERE, - Bind(&RecordRunTimeFunc, &run_time1, &num_tasks), - TimeDelta::FromMilliseconds(200)); - // If we get a large pause in execution (due to a context switch) here, this - // test could fail. - loop.PostDelayedTask( - FROM_HERE, - Bind(&RecordRunTimeFunc, &run_time2, &num_tasks), - TimeDelta::FromMilliseconds(10)); - - loop.Run(); - EXPECT_EQ(0, num_tasks); - - EXPECT_TRUE(run_time2 < run_time1); -} - -void RunTest_PostDelayedTask_InPostOrder( - MessageLoop::Type message_loop_type) { - MessageLoop loop(message_loop_type); - - // Test that two tasks with the same delay run in the order in which they - // were posted. - // - // NOTE: This is actually an approximate test since the API only takes a - // "delay" parameter, so we are not exactly simulating two tasks that get - // posted at the exact same time. It would be nice if the API allowed us to - // specify the desired run time. - - const TimeDelta kDelay = TimeDelta::FromMilliseconds(100); - - int num_tasks = 2; - Time run_time1, run_time2; - - loop.PostDelayedTask( - FROM_HERE, - Bind(&RecordRunTimeFunc, &run_time1, &num_tasks), kDelay); - loop.PostDelayedTask( - FROM_HERE, - Bind(&RecordRunTimeFunc, &run_time2, &num_tasks), kDelay); - - loop.Run(); - EXPECT_EQ(0, num_tasks); - - EXPECT_TRUE(run_time1 < run_time2); -} - -void RunTest_PostDelayedTask_InPostOrder_2( - MessageLoop::Type message_loop_type) { - MessageLoop loop(message_loop_type); - - // Test that a delayed task still runs after a normal tasks even if the - // normal tasks take a long time to run. - - const TimeDelta kPause = TimeDelta::FromMilliseconds(50); - - int num_tasks = 2; - Time run_time; - - loop.PostTask(FROM_HERE, Bind(&SlowFunc, kPause, &num_tasks)); - loop.PostDelayedTask( - FROM_HERE, - Bind(&RecordRunTimeFunc, &run_time, &num_tasks), - TimeDelta::FromMilliseconds(10)); - - Time time_before_run = Time::Now(); - loop.Run(); - Time time_after_run = Time::Now(); - - EXPECT_EQ(0, num_tasks); - - EXPECT_LT(kPause, time_after_run - time_before_run); -} - -void RunTest_PostDelayedTask_InPostOrder_3( - MessageLoop::Type message_loop_type) { - MessageLoop loop(message_loop_type); - - // Test that a delayed task still runs after a pile of normal tasks. The key - // difference between this test and the previous one is that here we return - // the MessageLoop a lot so we give the MessageLoop plenty of opportunities - // to maybe run the delayed task. It should know not to do so until the - // delayed task's delay has passed. - - int num_tasks = 11; - Time run_time1, run_time2; - - // Clutter the ML with tasks. - for (int i = 1; i < num_tasks; ++i) - loop.PostTask(FROM_HERE, - Bind(&RecordRunTimeFunc, &run_time1, &num_tasks)); - - loop.PostDelayedTask( - FROM_HERE, Bind(&RecordRunTimeFunc, &run_time2, &num_tasks), - TimeDelta::FromMilliseconds(1)); - - loop.Run(); - EXPECT_EQ(0, num_tasks); - - EXPECT_TRUE(run_time2 > run_time1); -} - -void RunTest_PostDelayedTask_SharedTimer( - MessageLoop::Type message_loop_type) { - MessageLoop loop(message_loop_type); - - // Test that the interval of the timer, used to run the next delayed task, is - // set to a value corresponding to when the next delayed task should run. - - // By setting num_tasks to 1, we ensure that the first task to run causes the - // run loop to exit. - int num_tasks = 1; - Time run_time1, run_time2; - - loop.PostDelayedTask( - FROM_HERE, - Bind(&RecordRunTimeFunc, &run_time1, &num_tasks), - TimeDelta::FromSeconds(1000)); - loop.PostDelayedTask( - FROM_HERE, - Bind(&RecordRunTimeFunc, &run_time2, &num_tasks), - TimeDelta::FromMilliseconds(10)); - - Time start_time = Time::Now(); - - loop.Run(); - EXPECT_EQ(0, num_tasks); - - // Ensure that we ran in far less time than the slower timer. - TimeDelta total_time = Time::Now() - start_time; - EXPECT_GT(5000, total_time.InMilliseconds()); - - // In case both timers somehow run at nearly the same time, sleep a little - // and then run all pending to force them both to have run. This is just - // encouraging flakiness if there is any. - PlatformThread::Sleep(TimeDelta::FromMilliseconds(100)); - RunLoop().RunUntilIdle(); - - EXPECT_TRUE(run_time1.is_null()); - EXPECT_FALSE(run_time2.is_null()); -} - -#if defined(OS_WIN) - void SubPumpFunc() { MessageLoop::current()->SetNestableTasksAllowed(true); MSG msg; @@ -407,82 +167,6 @@ void RunTest_PostDelayedTask_SharedTimer_SubPump() { EXPECT_TRUE(run_time.is_null()); } -#endif // defined(OS_WIN) - -// This is used to inject a test point for recording the destructor calls for -// Closure objects send to MessageLoop::PostTask(). It is awkward usage since we -// are trying to hook the actual destruction, which is not a common operation. -class RecordDeletionProbe : public RefCounted<RecordDeletionProbe> { - public: - RecordDeletionProbe(RecordDeletionProbe* post_on_delete, bool* was_deleted) - : post_on_delete_(post_on_delete), was_deleted_(was_deleted) { - } - void Run() {} - - private: - friend class RefCounted<RecordDeletionProbe>; - - ~RecordDeletionProbe() { - *was_deleted_ = true; - if (post_on_delete_.get()) - MessageLoop::current()->PostTask( - FROM_HERE, Bind(&RecordDeletionProbe::Run, post_on_delete_.get())); - } - - scoped_refptr<RecordDeletionProbe> post_on_delete_; - bool* was_deleted_; -}; - -void RunTest_EnsureDeletion(MessageLoop::Type message_loop_type) { - bool a_was_deleted = false; - bool b_was_deleted = false; - { - MessageLoop loop(message_loop_type); - loop.PostTask( - FROM_HERE, Bind(&RecordDeletionProbe::Run, - new RecordDeletionProbe(NULL, &a_was_deleted))); - // TODO(ajwong): Do we really need 1000ms here? - loop.PostDelayedTask( - FROM_HERE, Bind(&RecordDeletionProbe::Run, - new RecordDeletionProbe(NULL, &b_was_deleted)), - TimeDelta::FromMilliseconds(1000)); - } - EXPECT_TRUE(a_was_deleted); - EXPECT_TRUE(b_was_deleted); -} - -void RunTest_EnsureDeletion_Chain(MessageLoop::Type message_loop_type) { - bool a_was_deleted = false; - bool b_was_deleted = false; - bool c_was_deleted = false; - { - MessageLoop loop(message_loop_type); - // The scoped_refptr for each of the below is held either by the chained - // RecordDeletionProbe, or the bound RecordDeletionProbe::Run() callback. - RecordDeletionProbe* a = new RecordDeletionProbe(NULL, &a_was_deleted); - RecordDeletionProbe* b = new RecordDeletionProbe(a, &b_was_deleted); - RecordDeletionProbe* c = new RecordDeletionProbe(b, &c_was_deleted); - loop.PostTask(FROM_HERE, Bind(&RecordDeletionProbe::Run, c)); - } - EXPECT_TRUE(a_was_deleted); - EXPECT_TRUE(b_was_deleted); - EXPECT_TRUE(c_was_deleted); -} - -void NestingFunc(int* depth) { - if (*depth > 0) { - *depth -= 1; - MessageLoop::current()->PostTask(FROM_HERE, - Bind(&NestingFunc, depth)); - - MessageLoop::current()->SetNestableTasksAllowed(true); - MessageLoop::current()->Run(); - } - MessageLoop::current()->QuitWhenIdle(); -} - -#if defined(OS_WIN) - LONG WINAPI BadExceptionHandler(EXCEPTION_POINTERS *ex_info) { ADD_FAILURE() << "bad exception handler"; ::ExitProcess(ex_info->ExceptionRecord->ExceptionCode); @@ -597,19 +281,7 @@ void RunTest_CrasherNasty(MessageLoop::Type message_loop_type) { ::SetUnhandledExceptionFilter(old_SEH_filter); } -#endif // defined(OS_WIN) - -void RunTest_Nesting(MessageLoop::Type message_loop_type) { - MessageLoop loop(message_loop_type); - - int depth = 100; - MessageLoop::current()->PostTask(FROM_HERE, - Bind(&NestingFunc, &depth)); - MessageLoop::current()->Run(); - EXPECT_EQ(depth, 0); -} - -const wchar_t* const kMessageBoxTitle = L"MessageLoop Unit Test"; +const wchar_t kMessageBoxTitle[] = L"MessageLoop Unit Test"; enum TaskType { MESSAGEBOX, @@ -691,14 +363,6 @@ class TaskList { std::vector<TaskItem> task_list_; }; -// Saves the order the tasks ran. -void OrderedFunc(TaskList* order, int cookie) { - order->RecordStart(ORDERED, cookie); - order->RecordEnd(ORDERED, cookie); -} - -#if defined(OS_WIN) - // MessageLoop implicitly start a "modal message loop". Modal dialog boxes, // common controls (like OpenFile) and StartDoc printing function can cause // implicit message loops. @@ -722,8 +386,6 @@ void EndDialogFunc(TaskList* order, int cookie) { } } -#endif // defined(OS_WIN) - void RecursiveFunc(TaskList* order, int cookie, int depth, bool is_reentrant) { order->RecordStart(RECURSIVE, cookie); @@ -737,25 +399,12 @@ void RecursiveFunc(TaskList* order, int cookie, int depth, order->RecordEnd(RECURSIVE, cookie); } -void RecursiveSlowFunc(TaskList* order, int cookie, int depth, - bool is_reentrant) { - RecursiveFunc(order, cookie, depth, is_reentrant); - PlatformThread::Sleep(TimeDelta::FromMilliseconds(10)); -} - void QuitFunc(TaskList* order, int cookie) { order->RecordStart(QUITMESSAGELOOP, cookie); MessageLoop::current()->QuitWhenIdle(); order->RecordEnd(QUITMESSAGELOOP, cookie); } -void SleepFunc(TaskList* order, int cookie, TimeDelta delay) { - order->RecordStart(SLEEP, cookie); - PlatformThread::Sleep(delay); - order->RecordEnd(SLEEP, cookie); -} - -#if defined(OS_WIN) void RecursiveFuncWin(MessageLoop* target, HANDLE event, bool expect_window, @@ -801,115 +450,6 @@ void RecursiveFuncWin(MessageLoop* target, } } -#endif // defined(OS_WIN) - -void RunTest_RecursiveDenial1(MessageLoop::Type message_loop_type) { - MessageLoop loop(message_loop_type); - - EXPECT_TRUE(MessageLoop::current()->NestableTasksAllowed()); - TaskList order; - MessageLoop::current()->PostTask( - FROM_HERE, - Bind(&RecursiveFunc, &order, 1, 2, false)); - MessageLoop::current()->PostTask( - FROM_HERE, - Bind(&RecursiveFunc, &order, 2, 2, false)); - MessageLoop::current()->PostTask( - FROM_HERE, - Bind(&QuitFunc, &order, 3)); - - MessageLoop::current()->Run(); - - // FIFO order. - ASSERT_EQ(14U, order.Size()); - EXPECT_EQ(order.Get(0), TaskItem(RECURSIVE, 1, true)); - EXPECT_EQ(order.Get(1), TaskItem(RECURSIVE, 1, false)); - EXPECT_EQ(order.Get(2), TaskItem(RECURSIVE, 2, true)); - EXPECT_EQ(order.Get(3), TaskItem(RECURSIVE, 2, false)); - EXPECT_EQ(order.Get(4), TaskItem(QUITMESSAGELOOP, 3, true)); - EXPECT_EQ(order.Get(5), TaskItem(QUITMESSAGELOOP, 3, false)); - EXPECT_EQ(order.Get(6), TaskItem(RECURSIVE, 1, true)); - EXPECT_EQ(order.Get(7), TaskItem(RECURSIVE, 1, false)); - EXPECT_EQ(order.Get(8), TaskItem(RECURSIVE, 2, true)); - EXPECT_EQ(order.Get(9), TaskItem(RECURSIVE, 2, false)); - EXPECT_EQ(order.Get(10), TaskItem(RECURSIVE, 1, true)); - EXPECT_EQ(order.Get(11), TaskItem(RECURSIVE, 1, false)); - EXPECT_EQ(order.Get(12), TaskItem(RECURSIVE, 2, true)); - EXPECT_EQ(order.Get(13), TaskItem(RECURSIVE, 2, false)); -} - -void RunTest_RecursiveDenial3(MessageLoop::Type message_loop_type) { - MessageLoop loop(message_loop_type); - - EXPECT_TRUE(MessageLoop::current()->NestableTasksAllowed()); - TaskList order; - MessageLoop::current()->PostTask( - FROM_HERE, Bind(&RecursiveSlowFunc, &order, 1, 2, false)); - MessageLoop::current()->PostTask( - FROM_HERE, Bind(&RecursiveSlowFunc, &order, 2, 2, false)); - MessageLoop::current()->PostDelayedTask( - FROM_HERE, - Bind(&OrderedFunc, &order, 3), - TimeDelta::FromMilliseconds(5)); - MessageLoop::current()->PostDelayedTask( - FROM_HERE, - Bind(&QuitFunc, &order, 4), - TimeDelta::FromMilliseconds(5)); - - MessageLoop::current()->Run(); - - // FIFO order. - ASSERT_EQ(16U, order.Size()); - EXPECT_EQ(order.Get(0), TaskItem(RECURSIVE, 1, true)); - EXPECT_EQ(order.Get(1), TaskItem(RECURSIVE, 1, false)); - EXPECT_EQ(order.Get(2), TaskItem(RECURSIVE, 2, true)); - EXPECT_EQ(order.Get(3), TaskItem(RECURSIVE, 2, false)); - EXPECT_EQ(order.Get(4), TaskItem(RECURSIVE, 1, true)); - EXPECT_EQ(order.Get(5), TaskItem(RECURSIVE, 1, false)); - EXPECT_EQ(order.Get(6), TaskItem(ORDERED, 3, true)); - EXPECT_EQ(order.Get(7), TaskItem(ORDERED, 3, false)); - EXPECT_EQ(order.Get(8), TaskItem(RECURSIVE, 2, true)); - EXPECT_EQ(order.Get(9), TaskItem(RECURSIVE, 2, false)); - EXPECT_EQ(order.Get(10), TaskItem(QUITMESSAGELOOP, 4, true)); - EXPECT_EQ(order.Get(11), TaskItem(QUITMESSAGELOOP, 4, false)); - EXPECT_EQ(order.Get(12), TaskItem(RECURSIVE, 1, true)); - EXPECT_EQ(order.Get(13), TaskItem(RECURSIVE, 1, false)); - EXPECT_EQ(order.Get(14), TaskItem(RECURSIVE, 2, true)); - EXPECT_EQ(order.Get(15), TaskItem(RECURSIVE, 2, false)); -} - -void RunTest_RecursiveSupport1(MessageLoop::Type message_loop_type) { - MessageLoop loop(message_loop_type); - - TaskList order; - MessageLoop::current()->PostTask( - FROM_HERE, Bind(&RecursiveFunc, &order, 1, 2, true)); - MessageLoop::current()->PostTask( - FROM_HERE, Bind(&RecursiveFunc, &order, 2, 2, true)); - MessageLoop::current()->PostTask( - FROM_HERE, Bind(&QuitFunc, &order, 3)); - - MessageLoop::current()->Run(); - - // FIFO order. - ASSERT_EQ(14U, order.Size()); - EXPECT_EQ(order.Get(0), TaskItem(RECURSIVE, 1, true)); - EXPECT_EQ(order.Get(1), TaskItem(RECURSIVE, 1, false)); - EXPECT_EQ(order.Get(2), TaskItem(RECURSIVE, 2, true)); - EXPECT_EQ(order.Get(3), TaskItem(RECURSIVE, 2, false)); - EXPECT_EQ(order.Get(4), TaskItem(QUITMESSAGELOOP, 3, true)); - EXPECT_EQ(order.Get(5), TaskItem(QUITMESSAGELOOP, 3, false)); - EXPECT_EQ(order.Get(6), TaskItem(RECURSIVE, 1, true)); - EXPECT_EQ(order.Get(7), TaskItem(RECURSIVE, 1, false)); - EXPECT_EQ(order.Get(8), TaskItem(RECURSIVE, 2, true)); - EXPECT_EQ(order.Get(9), TaskItem(RECURSIVE, 2, false)); - EXPECT_EQ(order.Get(10), TaskItem(RECURSIVE, 1, true)); - EXPECT_EQ(order.Get(11), TaskItem(RECURSIVE, 1, false)); - EXPECT_EQ(order.Get(12), TaskItem(RECURSIVE, 2, true)); - EXPECT_EQ(order.Get(13), TaskItem(RECURSIVE, 2, false)); -} - -#if defined(OS_WIN) // TODO(darin): These tests need to be ported since they test critical // message loop functionality. @@ -1007,387 +547,6 @@ void RunTest_RecursiveSupport2(MessageLoop::Type message_loop_type) { #endif // defined(OS_WIN) -void FuncThatPumps(TaskList* order, int cookie) { - order->RecordStart(PUMPS, cookie); - { - MessageLoop::ScopedNestableTaskAllower allow(MessageLoop::current()); - RunLoop().RunUntilIdle(); - } - order->RecordEnd(PUMPS, cookie); -} - -void FuncThatRuns(TaskList* order, int cookie, RunLoop* run_loop) { - order->RecordStart(RUNS, cookie); - { - MessageLoop::ScopedNestableTaskAllower allow(MessageLoop::current()); - run_loop->Run(); - } - order->RecordEnd(RUNS, cookie); -} - -void FuncThatQuitsNow() { - MessageLoop::current()->QuitNow(); -} - -// Tests that non nestable tasks run in FIFO if there are no nested loops. -void RunTest_NonNestableWithNoNesting( - MessageLoop::Type message_loop_type) { - MessageLoop loop(message_loop_type); - - TaskList order; - - MessageLoop::current()->PostNonNestableTask( - FROM_HERE, - Bind(&OrderedFunc, &order, 1)); - MessageLoop::current()->PostTask(FROM_HERE, - Bind(&OrderedFunc, &order, 2)); - MessageLoop::current()->PostTask(FROM_HERE, - Bind(&QuitFunc, &order, 3)); - MessageLoop::current()->Run(); - - // FIFO order. - ASSERT_EQ(6U, order.Size()); - EXPECT_EQ(order.Get(0), TaskItem(ORDERED, 1, true)); - EXPECT_EQ(order.Get(1), TaskItem(ORDERED, 1, false)); - EXPECT_EQ(order.Get(2), TaskItem(ORDERED, 2, true)); - EXPECT_EQ(order.Get(3), TaskItem(ORDERED, 2, false)); - EXPECT_EQ(order.Get(4), TaskItem(QUITMESSAGELOOP, 3, true)); - EXPECT_EQ(order.Get(5), TaskItem(QUITMESSAGELOOP, 3, false)); -} - -// Tests that non nestable tasks don't run when there's code in the call stack. -void RunTest_NonNestableInNestedLoop(MessageLoop::Type message_loop_type, - bool use_delayed) { - MessageLoop loop(message_loop_type); - - TaskList order; - - MessageLoop::current()->PostTask( - FROM_HERE, - Bind(&FuncThatPumps, &order, 1)); - if (use_delayed) { - MessageLoop::current()->PostNonNestableDelayedTask( - FROM_HERE, - Bind(&OrderedFunc, &order, 2), - TimeDelta::FromMilliseconds(1)); - } else { - MessageLoop::current()->PostNonNestableTask( - FROM_HERE, - Bind(&OrderedFunc, &order, 2)); - } - MessageLoop::current()->PostTask(FROM_HERE, - Bind(&OrderedFunc, &order, 3)); - MessageLoop::current()->PostTask( - FROM_HERE, - Bind(&SleepFunc, &order, 4, TimeDelta::FromMilliseconds(50))); - MessageLoop::current()->PostTask(FROM_HERE, - Bind(&OrderedFunc, &order, 5)); - if (use_delayed) { - MessageLoop::current()->PostNonNestableDelayedTask( - FROM_HERE, - Bind(&QuitFunc, &order, 6), - TimeDelta::FromMilliseconds(2)); - } else { - MessageLoop::current()->PostNonNestableTask( - FROM_HERE, - Bind(&QuitFunc, &order, 6)); - } - - MessageLoop::current()->Run(); - - // FIFO order. - ASSERT_EQ(12U, order.Size()); - EXPECT_EQ(order.Get(0), TaskItem(PUMPS, 1, true)); - EXPECT_EQ(order.Get(1), TaskItem(ORDERED, 3, true)); - EXPECT_EQ(order.Get(2), TaskItem(ORDERED, 3, false)); - EXPECT_EQ(order.Get(3), TaskItem(SLEEP, 4, true)); - EXPECT_EQ(order.Get(4), TaskItem(SLEEP, 4, false)); - EXPECT_EQ(order.Get(5), TaskItem(ORDERED, 5, true)); - EXPECT_EQ(order.Get(6), TaskItem(ORDERED, 5, false)); - EXPECT_EQ(order.Get(7), TaskItem(PUMPS, 1, false)); - EXPECT_EQ(order.Get(8), TaskItem(ORDERED, 2, true)); - EXPECT_EQ(order.Get(9), TaskItem(ORDERED, 2, false)); - EXPECT_EQ(order.Get(10), TaskItem(QUITMESSAGELOOP, 6, true)); - EXPECT_EQ(order.Get(11), TaskItem(QUITMESSAGELOOP, 6, false)); -} - -// Tests RunLoopQuit only quits the corresponding MessageLoop::Run. -void RunTest_QuitNow(MessageLoop::Type message_loop_type) { - MessageLoop loop(message_loop_type); - - TaskList order; - - RunLoop run_loop; - - MessageLoop::current()->PostTask(FROM_HERE, - Bind(&FuncThatRuns, &order, 1, Unretained(&run_loop))); - MessageLoop::current()->PostTask( - FROM_HERE, Bind(&OrderedFunc, &order, 2)); - MessageLoop::current()->PostTask( - FROM_HERE, Bind(&FuncThatQuitsNow)); - MessageLoop::current()->PostTask( - FROM_HERE, Bind(&OrderedFunc, &order, 3)); - MessageLoop::current()->PostTask( - FROM_HERE, Bind(&FuncThatQuitsNow)); - MessageLoop::current()->PostTask( - FROM_HERE, Bind(&OrderedFunc, &order, 4)); // never runs - - MessageLoop::current()->Run(); - - ASSERT_EQ(6U, order.Size()); - int task_index = 0; - EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, true)); - EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, true)); - EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, false)); - EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, false)); - EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 3, true)); - EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 3, false)); - EXPECT_EQ(static_cast<size_t>(task_index), order.Size()); -} - -// Tests RunLoopQuit works before RunWithID. -void RunTest_RunLoopQuitOrderBefore(MessageLoop::Type message_loop_type) { - MessageLoop loop(message_loop_type); - - TaskList order; - - RunLoop run_loop; - - run_loop.Quit(); - - MessageLoop::current()->PostTask( - FROM_HERE, Bind(&OrderedFunc, &order, 1)); // never runs - MessageLoop::current()->PostTask( - FROM_HERE, Bind(&FuncThatQuitsNow)); // never runs - - run_loop.Run(); - - ASSERT_EQ(0U, order.Size()); -} - -// Tests RunLoopQuit works during RunWithID. -void RunTest_RunLoopQuitOrderDuring(MessageLoop::Type message_loop_type) { - MessageLoop loop(message_loop_type); - - TaskList order; - - RunLoop run_loop; - - MessageLoop::current()->PostTask( - FROM_HERE, Bind(&OrderedFunc, &order, 1)); - MessageLoop::current()->PostTask( - FROM_HERE, run_loop.QuitClosure()); - MessageLoop::current()->PostTask( - FROM_HERE, Bind(&OrderedFunc, &order, 2)); // never runs - MessageLoop::current()->PostTask( - FROM_HERE, Bind(&FuncThatQuitsNow)); // never runs - - run_loop.Run(); - - ASSERT_EQ(2U, order.Size()); - int task_index = 0; - EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 1, true)); - EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 1, false)); - EXPECT_EQ(static_cast<size_t>(task_index), order.Size()); -} - -// Tests RunLoopQuit works after RunWithID. -void RunTest_RunLoopQuitOrderAfter(MessageLoop::Type message_loop_type) { - MessageLoop loop(message_loop_type); - - TaskList order; - - RunLoop run_loop; - - MessageLoop::current()->PostTask(FROM_HERE, - Bind(&FuncThatRuns, &order, 1, Unretained(&run_loop))); - MessageLoop::current()->PostTask( - FROM_HERE, Bind(&OrderedFunc, &order, 2)); - MessageLoop::current()->PostTask( - FROM_HERE, Bind(&FuncThatQuitsNow)); - MessageLoop::current()->PostTask( - FROM_HERE, Bind(&OrderedFunc, &order, 3)); - MessageLoop::current()->PostTask( - FROM_HERE, run_loop.QuitClosure()); // has no affect - MessageLoop::current()->PostTask( - FROM_HERE, Bind(&OrderedFunc, &order, 4)); - MessageLoop::current()->PostTask( - FROM_HERE, Bind(&FuncThatQuitsNow)); - - RunLoop outer_run_loop; - outer_run_loop.Run(); - - ASSERT_EQ(8U, order.Size()); - int task_index = 0; - EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, true)); - EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, true)); - EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, false)); - EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, false)); - EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 3, true)); - EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 3, false)); - EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 4, true)); - EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 4, false)); - EXPECT_EQ(static_cast<size_t>(task_index), order.Size()); -} - -// Tests RunLoopQuit only quits the corresponding MessageLoop::Run. -void RunTest_RunLoopQuitTop(MessageLoop::Type message_loop_type) { - MessageLoop loop(message_loop_type); - - TaskList order; - - RunLoop outer_run_loop; - RunLoop nested_run_loop; - - MessageLoop::current()->PostTask(FROM_HERE, - Bind(&FuncThatRuns, &order, 1, Unretained(&nested_run_loop))); - MessageLoop::current()->PostTask( - FROM_HERE, outer_run_loop.QuitClosure()); - MessageLoop::current()->PostTask( - FROM_HERE, Bind(&OrderedFunc, &order, 2)); - MessageLoop::current()->PostTask( - FROM_HERE, nested_run_loop.QuitClosure()); - - outer_run_loop.Run(); - - ASSERT_EQ(4U, order.Size()); - int task_index = 0; - EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, true)); - EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, true)); - EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, false)); - EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, false)); - EXPECT_EQ(static_cast<size_t>(task_index), order.Size()); -} - -// Tests RunLoopQuit only quits the corresponding MessageLoop::Run. -void RunTest_RunLoopQuitNested(MessageLoop::Type message_loop_type) { - MessageLoop loop(message_loop_type); - - TaskList order; - - RunLoop outer_run_loop; - RunLoop nested_run_loop; - - MessageLoop::current()->PostTask(FROM_HERE, - Bind(&FuncThatRuns, &order, 1, Unretained(&nested_run_loop))); - MessageLoop::current()->PostTask( - FROM_HERE, nested_run_loop.QuitClosure()); - MessageLoop::current()->PostTask( - FROM_HERE, Bind(&OrderedFunc, &order, 2)); - MessageLoop::current()->PostTask( - FROM_HERE, outer_run_loop.QuitClosure()); - - outer_run_loop.Run(); - - ASSERT_EQ(4U, order.Size()); - int task_index = 0; - EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, true)); - EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, false)); - EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, true)); - EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, false)); - EXPECT_EQ(static_cast<size_t>(task_index), order.Size()); -} - -// Tests RunLoopQuit only quits the corresponding MessageLoop::Run. -void RunTest_RunLoopQuitBogus(MessageLoop::Type message_loop_type) { - MessageLoop loop(message_loop_type); - - TaskList order; - - RunLoop outer_run_loop; - RunLoop nested_run_loop; - RunLoop bogus_run_loop; - - MessageLoop::current()->PostTask(FROM_HERE, - Bind(&FuncThatRuns, &order, 1, Unretained(&nested_run_loop))); - MessageLoop::current()->PostTask( - FROM_HERE, bogus_run_loop.QuitClosure()); - MessageLoop::current()->PostTask( - FROM_HERE, Bind(&OrderedFunc, &order, 2)); - MessageLoop::current()->PostTask( - FROM_HERE, outer_run_loop.QuitClosure()); - MessageLoop::current()->PostTask( - FROM_HERE, nested_run_loop.QuitClosure()); - - outer_run_loop.Run(); - - ASSERT_EQ(4U, order.Size()); - int task_index = 0; - EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, true)); - EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, true)); - EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 2, false)); - EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, false)); - EXPECT_EQ(static_cast<size_t>(task_index), order.Size()); -} - -// Tests RunLoopQuit only quits the corresponding MessageLoop::Run. -void RunTest_RunLoopQuitDeep(MessageLoop::Type message_loop_type) { - MessageLoop loop(message_loop_type); - - TaskList order; - - RunLoop outer_run_loop; - RunLoop nested_loop1; - RunLoop nested_loop2; - RunLoop nested_loop3; - RunLoop nested_loop4; - - MessageLoop::current()->PostTask(FROM_HERE, - Bind(&FuncThatRuns, &order, 1, Unretained(&nested_loop1))); - MessageLoop::current()->PostTask(FROM_HERE, - Bind(&FuncThatRuns, &order, 2, Unretained(&nested_loop2))); - MessageLoop::current()->PostTask(FROM_HERE, - Bind(&FuncThatRuns, &order, 3, Unretained(&nested_loop3))); - MessageLoop::current()->PostTask(FROM_HERE, - Bind(&FuncThatRuns, &order, 4, Unretained(&nested_loop4))); - MessageLoop::current()->PostTask( - FROM_HERE, Bind(&OrderedFunc, &order, 5)); - MessageLoop::current()->PostTask( - FROM_HERE, outer_run_loop.QuitClosure()); - MessageLoop::current()->PostTask( - FROM_HERE, Bind(&OrderedFunc, &order, 6)); - MessageLoop::current()->PostTask( - FROM_HERE, nested_loop1.QuitClosure()); - MessageLoop::current()->PostTask( - FROM_HERE, Bind(&OrderedFunc, &order, 7)); - MessageLoop::current()->PostTask( - FROM_HERE, nested_loop2.QuitClosure()); - MessageLoop::current()->PostTask( - FROM_HERE, Bind(&OrderedFunc, &order, 8)); - MessageLoop::current()->PostTask( - FROM_HERE, nested_loop3.QuitClosure()); - MessageLoop::current()->PostTask( - FROM_HERE, Bind(&OrderedFunc, &order, 9)); - MessageLoop::current()->PostTask( - FROM_HERE, nested_loop4.QuitClosure()); - MessageLoop::current()->PostTask( - FROM_HERE, Bind(&OrderedFunc, &order, 10)); - - outer_run_loop.Run(); - - ASSERT_EQ(18U, order.Size()); - int task_index = 0; - EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, true)); - EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 2, true)); - EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 3, true)); - EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 4, true)); - EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 5, true)); - EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 5, false)); - EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 6, true)); - EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 6, false)); - EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 7, true)); - EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 7, false)); - EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 8, true)); - EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 8, false)); - EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 9, true)); - EXPECT_EQ(order.Get(task_index++), TaskItem(ORDERED, 9, false)); - EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 4, false)); - EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 3, false)); - EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 2, false)); - EXPECT_EQ(order.Get(task_index++), TaskItem(RUNS, 1, false)); - EXPECT_EQ(static_cast<size_t>(task_index), order.Size()); -} - void PostNTasksThenQuit(int posts_remaining) { if (posts_remaining > 1) { MessageLoop::current()->PostTask( @@ -1398,13 +557,6 @@ void PostNTasksThenQuit(int posts_remaining) { } } -void RunTest_RecursivePosts(MessageLoop::Type message_loop_type, - int num_times) { - MessageLoop loop(message_loop_type); - loop.PostTask(FROM_HERE, Bind(&PostNTasksThenQuit, num_times)); - loop.Run(); -} - #if defined(OS_WIN) class DispatcherImpl : public MessageLoopForUI::Dispatcher { @@ -1620,79 +772,15 @@ void RunTest_WaitForIO() { // that message loops work properly in all configurations. Of course, in some // cases, a unit test may only be for a particular type of loop. -TEST(MessageLoopTest, PostTask) { - RunTest_PostTask(MessageLoop::TYPE_DEFAULT); - RunTest_PostTask(MessageLoop::TYPE_UI); - RunTest_PostTask(MessageLoop::TYPE_IO); -} - -TEST(MessageLoopTest, PostTask_SEH) { - RunTest_PostTask_SEH(MessageLoop::TYPE_DEFAULT); - RunTest_PostTask_SEH(MessageLoop::TYPE_UI); - RunTest_PostTask_SEH(MessageLoop::TYPE_IO); -} - -TEST(MessageLoopTest, PostDelayedTask_Basic) { - RunTest_PostDelayedTask_Basic(MessageLoop::TYPE_DEFAULT); - RunTest_PostDelayedTask_Basic(MessageLoop::TYPE_UI); - RunTest_PostDelayedTask_Basic(MessageLoop::TYPE_IO); -} - -TEST(MessageLoopTest, PostDelayedTask_InDelayOrder) { - RunTest_PostDelayedTask_InDelayOrder(MessageLoop::TYPE_DEFAULT); - RunTest_PostDelayedTask_InDelayOrder(MessageLoop::TYPE_UI); - RunTest_PostDelayedTask_InDelayOrder(MessageLoop::TYPE_IO); -} - -TEST(MessageLoopTest, PostDelayedTask_InPostOrder) { - RunTest_PostDelayedTask_InPostOrder(MessageLoop::TYPE_DEFAULT); - RunTest_PostDelayedTask_InPostOrder(MessageLoop::TYPE_UI); - RunTest_PostDelayedTask_InPostOrder(MessageLoop::TYPE_IO); -} - -TEST(MessageLoopTest, PostDelayedTask_InPostOrder_2) { - RunTest_PostDelayedTask_InPostOrder_2(MessageLoop::TYPE_DEFAULT); - RunTest_PostDelayedTask_InPostOrder_2(MessageLoop::TYPE_UI); - RunTest_PostDelayedTask_InPostOrder_2(MessageLoop::TYPE_IO); -} - -TEST(MessageLoopTest, PostDelayedTask_InPostOrder_3) { - RunTest_PostDelayedTask_InPostOrder_3(MessageLoop::TYPE_DEFAULT); - RunTest_PostDelayedTask_InPostOrder_3(MessageLoop::TYPE_UI); - RunTest_PostDelayedTask_InPostOrder_3(MessageLoop::TYPE_IO); -} - -TEST(MessageLoopTest, PostDelayedTask_SharedTimer) { - RunTest_PostDelayedTask_SharedTimer(MessageLoop::TYPE_DEFAULT); - RunTest_PostDelayedTask_SharedTimer(MessageLoop::TYPE_UI); - RunTest_PostDelayedTask_SharedTimer(MessageLoop::TYPE_IO); -} +RUN_MESSAGE_LOOP_TESTS(Default, &TypeDefaultMessagePumpFactory); +RUN_MESSAGE_LOOP_TESTS(UI, &TypeUIMessagePumpFactory); +RUN_MESSAGE_LOOP_TESTS(IO, &TypeIOMessagePumpFactory); #if defined(OS_WIN) TEST(MessageLoopTest, PostDelayedTask_SharedTimer_SubPump) { RunTest_PostDelayedTask_SharedTimer_SubPump(); } -#endif - -// TODO(darin): MessageLoop does not support deleting all tasks in the -// destructor. -// Fails, http://crbug.com/50272. -TEST(MessageLoopTest, DISABLED_EnsureDeletion) { - RunTest_EnsureDeletion(MessageLoop::TYPE_DEFAULT); - RunTest_EnsureDeletion(MessageLoop::TYPE_UI); - RunTest_EnsureDeletion(MessageLoop::TYPE_IO); -} - -// TODO(darin): MessageLoop does not support deleting all tasks in the -// destructor. -// Fails, http://crbug.com/50272. -TEST(MessageLoopTest, DISABLED_EnsureDeletion_Chain) { - RunTest_EnsureDeletion_Chain(MessageLoop::TYPE_DEFAULT); - RunTest_EnsureDeletion_Chain(MessageLoop::TYPE_UI); - RunTest_EnsureDeletion_Chain(MessageLoop::TYPE_IO); -} -#if defined(OS_WIN) TEST(MessageLoopTest, Crasher) { RunTest_Crasher(MessageLoop::TYPE_DEFAULT); RunTest_Crasher(MessageLoop::TYPE_UI); @@ -1704,33 +792,7 @@ TEST(MessageLoopTest, CrasherNasty) { RunTest_CrasherNasty(MessageLoop::TYPE_UI); RunTest_CrasherNasty(MessageLoop::TYPE_IO); } -#endif // defined(OS_WIN) - -TEST(MessageLoopTest, Nesting) { - RunTest_Nesting(MessageLoop::TYPE_DEFAULT); - RunTest_Nesting(MessageLoop::TYPE_UI); - RunTest_Nesting(MessageLoop::TYPE_IO); -} - -TEST(MessageLoopTest, RecursiveDenial1) { - RunTest_RecursiveDenial1(MessageLoop::TYPE_DEFAULT); - RunTest_RecursiveDenial1(MessageLoop::TYPE_UI); - RunTest_RecursiveDenial1(MessageLoop::TYPE_IO); -} - -TEST(MessageLoopTest, RecursiveDenial3) { - RunTest_RecursiveDenial3(MessageLoop::TYPE_DEFAULT); - RunTest_RecursiveDenial3(MessageLoop::TYPE_UI); - RunTest_RecursiveDenial3(MessageLoop::TYPE_IO); -} -TEST(MessageLoopTest, RecursiveSupport1) { - RunTest_RecursiveSupport1(MessageLoop::TYPE_DEFAULT); - RunTest_RecursiveSupport1(MessageLoop::TYPE_UI); - RunTest_RecursiveSupport1(MessageLoop::TYPE_IO); -} - -#if defined(OS_WIN) // This test occasionally hangs http://crbug.com/44567 TEST(MessageLoopTest, DISABLED_RecursiveDenial2) { RunTest_RecursiveDenial2(MessageLoop::TYPE_DEFAULT); @@ -1744,72 +806,6 @@ TEST(MessageLoopTest, RecursiveSupport2) { } #endif // defined(OS_WIN) -TEST(MessageLoopTest, NonNestableWithNoNesting) { - RunTest_NonNestableWithNoNesting(MessageLoop::TYPE_DEFAULT); - RunTest_NonNestableWithNoNesting(MessageLoop::TYPE_UI); - RunTest_NonNestableWithNoNesting(MessageLoop::TYPE_IO); -} - -TEST(MessageLoopTest, NonNestableInNestedLoop) { - RunTest_NonNestableInNestedLoop(MessageLoop::TYPE_DEFAULT, false); - RunTest_NonNestableInNestedLoop(MessageLoop::TYPE_UI, false); - RunTest_NonNestableInNestedLoop(MessageLoop::TYPE_IO, false); -} - -TEST(MessageLoopTest, NonNestableDelayedInNestedLoop) { - RunTest_NonNestableInNestedLoop(MessageLoop::TYPE_DEFAULT, true); - RunTest_NonNestableInNestedLoop(MessageLoop::TYPE_UI, true); - RunTest_NonNestableInNestedLoop(MessageLoop::TYPE_IO, true); -} - -TEST(MessageLoopTest, QuitNow) { - RunTest_QuitNow(MessageLoop::TYPE_DEFAULT); - RunTest_QuitNow(MessageLoop::TYPE_UI); - RunTest_QuitNow(MessageLoop::TYPE_IO); -} - -TEST(MessageLoopTest, RunLoopQuitTop) { - RunTest_RunLoopQuitTop(MessageLoop::TYPE_DEFAULT); - RunTest_RunLoopQuitTop(MessageLoop::TYPE_UI); - RunTest_RunLoopQuitTop(MessageLoop::TYPE_IO); -} - -TEST(MessageLoopTest, RunLoopQuitNested) { - RunTest_RunLoopQuitNested(MessageLoop::TYPE_DEFAULT); - RunTest_RunLoopQuitNested(MessageLoop::TYPE_UI); - RunTest_RunLoopQuitNested(MessageLoop::TYPE_IO); -} - -TEST(MessageLoopTest, RunLoopQuitBogus) { - RunTest_RunLoopQuitBogus(MessageLoop::TYPE_DEFAULT); - RunTest_RunLoopQuitBogus(MessageLoop::TYPE_UI); - RunTest_RunLoopQuitBogus(MessageLoop::TYPE_IO); -} - -TEST(MessageLoopTest, RunLoopQuitDeep) { - RunTest_RunLoopQuitDeep(MessageLoop::TYPE_DEFAULT); - RunTest_RunLoopQuitDeep(MessageLoop::TYPE_UI); - RunTest_RunLoopQuitDeep(MessageLoop::TYPE_IO); -} - -TEST(MessageLoopTest, RunLoopQuitOrderBefore) { - RunTest_RunLoopQuitOrderBefore(MessageLoop::TYPE_DEFAULT); - RunTest_RunLoopQuitOrderBefore(MessageLoop::TYPE_UI); - RunTest_RunLoopQuitOrderBefore(MessageLoop::TYPE_IO); -} - -TEST(MessageLoopTest, RunLoopQuitOrderDuring) { - RunTest_RunLoopQuitOrderDuring(MessageLoop::TYPE_DEFAULT); - RunTest_RunLoopQuitOrderDuring(MessageLoop::TYPE_UI); - RunTest_RunLoopQuitOrderDuring(MessageLoop::TYPE_IO); -} - -TEST(MessageLoopTest, RunLoopQuitOrderAfter) { - RunTest_RunLoopQuitOrderAfter(MessageLoop::TYPE_DEFAULT); - RunTest_RunLoopQuitOrderAfter(MessageLoop::TYPE_UI); - RunTest_RunLoopQuitOrderAfter(MessageLoop::TYPE_IO); -} - class DummyTaskObserver : public MessageLoop::TaskObserver { public: explicit DummyTaskObserver(int num_tasks) @@ -1948,9 +944,9 @@ TEST(MessageLoopTest, FileDescriptorWatcherOutlivesMessageLoop) { // and don't run the message loop, just destroy it. } } - if (HANDLE_EINTR(close(pipefds[0])) < 0) + if (IGNORE_EINTR(close(pipefds[0])) < 0) PLOG(ERROR) << "close"; - if (HANDLE_EINTR(close(pipefds[1])) < 0) + if (IGNORE_EINTR(close(pipefds[1])) < 0) PLOG(ERROR) << "close"; } @@ -1973,9 +969,9 @@ TEST(MessageLoopTest, FileDescriptorWatcherDoubleStop) { controller.StopWatchingFileDescriptor(); } } - if (HANDLE_EINTR(close(pipefds[0])) < 0) + if (IGNORE_EINTR(close(pipefds[0])) < 0) PLOG(ERROR) << "close"; - if (HANDLE_EINTR(close(pipefds[1])) < 0) + if (IGNORE_EINTR(close(pipefds[1])) < 0) PLOG(ERROR) << "close"; } @@ -2086,19 +1082,92 @@ TEST(MessageLoopTest, IsType) { EXPECT_FALSE(loop.IsType(MessageLoop::TYPE_DEFAULT)); } -TEST(MessageLoopTest, RecursivePosts) { - // There was a bug in the MessagePumpGLib where posting tasks recursively - // caused the message loop to hang, due to the buffer of the internal pipe - // becoming full. Test all MessageLoop types to ensure this issue does not - // exist in other MessagePumps. - - // On Linux, the pipe buffer size is 64KiB by default. The bug caused one - // byte accumulated in the pipe per two posts, so we should repeat 128K - // times to reproduce the bug. - const int kNumTimes = 1 << 17; - RunTest_RecursivePosts(MessageLoop::TYPE_DEFAULT, kNumTimes); - RunTest_RecursivePosts(MessageLoop::TYPE_UI, kNumTimes); - RunTest_RecursivePosts(MessageLoop::TYPE_IO, kNumTimes); +#if defined(OS_WIN) +void EmptyFunction() {} + +void PostMultipleTasks() { + MessageLoop::current()->PostTask(FROM_HERE, base::Bind(&EmptyFunction)); + MessageLoop::current()->PostTask(FROM_HERE, base::Bind(&EmptyFunction)); +} + +static const int kSignalMsg = WM_USER + 2; + +void PostWindowsMessage(HWND message_hwnd) { + PostMessage(message_hwnd, kSignalMsg, 0, 2); +} + +void EndTest(bool* did_run, HWND hwnd) { + *did_run = true; + PostMessage(hwnd, WM_CLOSE, 0, 0); +} + +int kMyMessageFilterCode = 0x5002; + +LRESULT CALLBACK TestWndProcThunk(HWND hwnd, UINT message, + WPARAM wparam, LPARAM lparam) { + if (message == WM_CLOSE) + EXPECT_TRUE(DestroyWindow(hwnd)); + if (message != kSignalMsg) + return DefWindowProc(hwnd, message, wparam, lparam); + + switch (lparam) { + case 1: + // First, we post a task that will post multiple no-op tasks to make sure + // that the pump's incoming task queue does not become empty during the + // test. + MessageLoop::current()->PostTask(FROM_HERE, base::Bind(&PostMultipleTasks)); + // Next, we post a task that posts a windows message to trigger the second + // stage of the test. + MessageLoop::current()->PostTask(FROM_HERE, + base::Bind(&PostWindowsMessage, hwnd)); + break; + case 2: + // Since we're about to enter a modal loop, tell the message loop that we + // intend to nest tasks. + MessageLoop::current()->SetNestableTasksAllowed(true); + bool did_run = false; + MessageLoop::current()->PostTask(FROM_HERE, + base::Bind(&EndTest, &did_run, hwnd)); + // Run a nested windows-style message loop and verify that our task runs. If + // it doesn't, then we'll loop here until the test times out. + MSG msg; + while (GetMessage(&msg, 0, 0, 0)) { + if (!CallMsgFilter(&msg, kMyMessageFilterCode)) + DispatchMessage(&msg); + // If this message is a WM_CLOSE, explicitly exit the modal loop. Posting + // a WM_QUIT should handle this, but unfortunately MessagePumpWin eats + // WM_QUIT messages even when running inside a modal loop. + if (msg.message == WM_CLOSE) + break; + } + EXPECT_TRUE(did_run); + MessageLoop::current()->Quit(); + break; + } + return 0; +} + +TEST(MessageLoopTest, AlwaysHaveUserMessageWhenNesting) { + MessageLoop loop(MessageLoop::TYPE_UI); + HINSTANCE instance = GetModuleFromAddress(&TestWndProcThunk); + WNDCLASSEX wc = {0}; + wc.cbSize = sizeof(wc); + wc.lpfnWndProc = TestWndProcThunk; + wc.hInstance = instance; + wc.lpszClassName = L"MessageLoopTest_HWND"; + ATOM atom = RegisterClassEx(&wc); + ASSERT_TRUE(atom); + + HWND message_hwnd = CreateWindow(MAKEINTATOM(atom), 0, 0, 0, 0, 0, 0, + HWND_MESSAGE, 0, instance, 0); + ASSERT_TRUE(message_hwnd) << GetLastError(); + + ASSERT_TRUE(PostMessage(message_hwnd, kSignalMsg, 0, 1)); + + loop.Run(); + + ASSERT_TRUE(UnregisterClass(MAKEINTATOM(atom), instance)); } +#endif // defined(OS_WIN) } // namespace base diff --git a/chromium/base/message_loop/message_pump_android.cc b/chromium/base/message_loop/message_pump_android.cc index f3f1c9bcb4d..e756fdd3f52 100644 --- a/chromium/base/message_loop/message_pump_android.cc +++ b/chromium/base/message_loop/message_pump_android.cc @@ -21,7 +21,7 @@ using base::android::ScopedJavaLocalRef; // ---------------------------------------------------------------------------- // This method can not move to anonymous namespace as it has been declared as // 'static' in system_message_handler_jni.h. -static void DoRunLoopOnce(JNIEnv* env, jobject obj, jint native_delegate) { +static void DoRunLoopOnce(JNIEnv* env, jobject obj, jlong native_delegate) { base::MessagePump::Delegate* delegate = reinterpret_cast<base::MessagePump::Delegate*>(native_delegate); DCHECK(delegate); @@ -81,7 +81,8 @@ void MessagePumpForUI::Start(Delegate* delegate) { DCHECK(env); system_message_handler_obj_.Reset( - Java_SystemMessageHandler_create(env, reinterpret_cast<jint>(delegate))); + Java_SystemMessageHandler_create( + env, reinterpret_cast<intptr_t>(delegate))); } void MessagePumpForUI::Quit() { diff --git a/chromium/base/message_loop/message_pump_io_ios_unittest.cc b/chromium/base/message_loop/message_pump_io_ios_unittest.cc index 9c7a8fbf651..f3b598c6ba6 100644 --- a/chromium/base/message_loop/message_pump_io_ios_unittest.cc +++ b/chromium/base/message_loop/message_pump_io_ios_unittest.cc @@ -31,9 +31,9 @@ class MessagePumpIOSForIOTest : public testing::Test { } virtual void TearDown() OVERRIDE { - if (HANDLE_EINTR(close(pipefds_[0])) < 0) + if (IGNORE_EINTR(close(pipefds_[0])) < 0) PLOG(ERROR) << "close"; - if (HANDLE_EINTR(close(pipefds_[1])) < 0) + if (IGNORE_EINTR(close(pipefds_[1])) < 0) PLOG(ERROR) << "close"; } diff --git a/chromium/base/message_loop/message_pump_libevent.cc b/chromium/base/message_loop/message_pump_libevent.cc index 6d862d1d266..26be687c6dd 100644 --- a/chromium/base/message_loop/message_pump_libevent.cc +++ b/chromium/base/message_loop/message_pump_libevent.cc @@ -125,11 +125,11 @@ MessagePumpLibevent::~MessagePumpLibevent() { event_del(wakeup_event_); delete wakeup_event_; if (wakeup_pipe_in_ >= 0) { - if (HANDLE_EINTR(close(wakeup_pipe_in_)) < 0) + if (IGNORE_EINTR(close(wakeup_pipe_in_)) < 0) DPLOG(ERROR) << "close"; } if (wakeup_pipe_out_ >= 0) { - if (HANDLE_EINTR(close(wakeup_pipe_out_)) < 0) + if (IGNORE_EINTR(close(wakeup_pipe_out_)) < 0) DPLOG(ERROR) << "close"; } event_base_free(event_base_); diff --git a/chromium/base/message_loop/message_pump_libevent_unittest.cc b/chromium/base/message_loop/message_pump_libevent_unittest.cc index 52ca95bebf7..bf6d21c3280 100644 --- a/chromium/base/message_loop/message_pump_libevent_unittest.cc +++ b/chromium/base/message_loop/message_pump_libevent_unittest.cc @@ -30,9 +30,9 @@ class MessagePumpLibeventTest : public testing::Test { } virtual void TearDown() OVERRIDE { - if (HANDLE_EINTR(close(pipefds_[0])) < 0) + if (IGNORE_EINTR(close(pipefds_[0])) < 0) PLOG(ERROR) << "close"; - if (HANDLE_EINTR(close(pipefds_[1])) < 0) + if (IGNORE_EINTR(close(pipefds_[1])) < 0) PLOG(ERROR) << "close"; } diff --git a/chromium/base/message_loop/message_pump_win.cc b/chromium/base/message_loop/message_pump_win.cc index 589d0775410..1927473b1eb 100644 --- a/chromium/base/message_loop/message_pump_win.cc +++ b/chromium/base/message_loop/message_pump_win.cc @@ -172,30 +172,6 @@ void MessagePumpForUI::ScheduleDelayedWork(const TimeTicks& delayed_work_time) { MESSAGE_LOOP_PROBLEM_MAX); } -void MessagePumpForUI::PumpOutPendingPaintMessages() { - // If we are being called outside of the context of Run, then don't try to do - // any work. - if (!state_) - return; - - // Create a mini-message-pump to force immediate processing of only Windows - // WM_PAINT messages. Don't provide an infinite loop, but do enough peeking - // to get the job done. Actual common max is 4 peeks, but we'll be a little - // safe here. - const int kMaxPeekCount = 20; - int peek_count; - for (peek_count = 0; peek_count < kMaxPeekCount; ++peek_count) { - MSG msg; - if (!PeekMessage(&msg, NULL, 0, 0, PM_REMOVE | PM_QS_PAINT)) - break; - ProcessMessageHelper(msg); - if (state_->should_quit) // Handle WM_QUIT. - break; - } - // Histogram what was really being used, to help to adjust kMaxPeekCount. - DHISTOGRAM_COUNTS("Loop.PumpOutPendingPaintMessages Peeks", peek_count); -} - //----------------------------------------------------------------------------- // MessagePumpForUI private: diff --git a/chromium/base/message_loop/message_pump_win.h b/chromium/base/message_loop/message_pump_win.h index 2d833540e03..9184058a6c0 100644 --- a/chromium/base/message_loop/message_pump_win.h +++ b/chromium/base/message_loop/message_pump_win.h @@ -169,11 +169,6 @@ class BASE_EXPORT MessagePumpForUI : public MessagePumpWin { virtual void ScheduleWork(); virtual void ScheduleDelayedWork(const TimeTicks& delayed_work_time); - // Applications can call this to encourage us to process all pending WM_PAINT - // messages. This method will process all paint messages the Windows Message - // queue can provide, up to some fixed number (to avoid any infinite loops). - void PumpOutPendingPaintMessages(); - private: static LRESULT CALLBACK WndProcThunk(HWND window_handle, UINT message, diff --git a/chromium/base/message_loop/message_pump_x11.cc b/chromium/base/message_loop/message_pump_x11.cc index dd8b965e695..35dcc040348 100644 --- a/chromium/base/message_loop/message_pump_x11.cc +++ b/chromium/base/message_loop/message_pump_x11.cc @@ -53,7 +53,7 @@ GSourceFuncs XSourceFuncs = { Display* g_xdisplay = NULL; int g_xinput_opcode = -1; -bool InitializeXInput2Internal() { +bool InitializeXInput2() { Display* display = MessagePumpX11::GetDefaultXDisplay(); if (!display) return false; @@ -97,11 +97,6 @@ Window FindEventTarget(const NativeEvent& xev) { return target; } -bool InitializeXInput2() { - static bool xinput2_supported = InitializeXInput2Internal(); - return xinput2_supported; -} - bool InitializeXkb() { Display* display = MessagePumpX11::GetDefaultXDisplay(); if (!display) @@ -153,11 +148,6 @@ Display* MessagePumpX11::GetDefaultXDisplay() { return g_xdisplay; } -// static -bool MessagePumpX11::HasXInput2() { - return InitializeXInput2(); -} - #if defined(TOOLKIT_GTK) // static MessagePumpX11* MessagePumpX11::Current() { diff --git a/chromium/base/message_loop/message_pump_x11.h b/chromium/base/message_loop/message_pump_x11.h index f1f678a79c7..015c230a700 100644 --- a/chromium/base/message_loop/message_pump_x11.h +++ b/chromium/base/message_loop/message_pump_x11.h @@ -40,9 +40,6 @@ class BASE_EXPORT MessagePumpX11 : public MessagePumpGlib, // Returns default X Display. static Display* GetDefaultXDisplay(); - // Returns true if the system supports XINPUT2. - static bool HasXInput2(); - // Returns the UI or GPU message pump. static MessagePumpX11* Current(); diff --git a/chromium/base/metrics/field_trial.cc b/chromium/base/metrics/field_trial.cc index d500a29cad2..b99f3148f96 100644 --- a/chromium/base/metrics/field_trial.cc +++ b/chromium/base/metrics/field_trial.cc @@ -72,25 +72,6 @@ int FieldTrialList::kNoExpirationYear = 0; //------------------------------------------------------------------------------ // FieldTrial methods and members. -FieldTrial::FieldTrial(const std::string& trial_name, - const Probability total_probability, - const std::string& default_group_name, - double entropy_value) - : trial_name_(trial_name), - divisor_(total_probability), - default_group_name_(default_group_name), - random_(GetGroupBoundaryValue(total_probability, entropy_value)), - accumulated_group_probability_(0), - next_group_number_(kDefaultGroupNumber + 1), - group_(kNotFinalized), - enable_field_trial_(true), - forced_(false), - group_reported_(false) { - DCHECK_GT(total_probability, 0); - DCHECK(!trial_name_.empty()); - DCHECK(!default_group_name_.empty()); -} - FieldTrial::EntropyProvider::~EntropyProvider() { } @@ -146,7 +127,8 @@ int FieldTrial::AppendGroup(const std::string& name, int FieldTrial::group() { FinalizeGroupChoice(); - FieldTrialList::NotifyFieldTrialGroupSelection(this); + if (trial_registered_) + FieldTrialList::NotifyFieldTrialGroupSelection(this); return group_; } @@ -157,12 +139,6 @@ const std::string& FieldTrial::group_name() { return group_name_; } -// static -void FieldTrial::EnableBenchmarking() { - DCHECK_EQ(0u, FieldTrialList::GetFieldTrialCount()); - enable_benchmarking_ = true; -} - void FieldTrial::SetForced() { // We might have been forced before (e.g., by CreateFieldTrial) and it's // first come first served, e.g., command line switch has precedence. @@ -174,8 +150,50 @@ void FieldTrial::SetForced() { forced_ = true; } +// static +void FieldTrial::EnableBenchmarking() { + DCHECK_EQ(0u, FieldTrialList::GetFieldTrialCount()); + enable_benchmarking_ = true; +} + +// static +FieldTrial* FieldTrial::CreateSimulatedFieldTrial( + const std::string& trial_name, + Probability total_probability, + const std::string& default_group_name, + double entropy_value) { + return new FieldTrial(trial_name, total_probability, default_group_name, + entropy_value); +} + +FieldTrial::FieldTrial(const std::string& trial_name, + const Probability total_probability, + const std::string& default_group_name, + double entropy_value) + : trial_name_(trial_name), + divisor_(total_probability), + default_group_name_(default_group_name), + random_(GetGroupBoundaryValue(total_probability, entropy_value)), + accumulated_group_probability_(0), + next_group_number_(kDefaultGroupNumber + 1), + group_(kNotFinalized), + enable_field_trial_(true), + forced_(false), + group_reported_(false), + trial_registered_(false) { + DCHECK_GT(total_probability, 0); + DCHECK(!trial_name_.empty()); + DCHECK(!default_group_name_.empty()); +} + FieldTrial::~FieldTrial() {} +void FieldTrial::SetTrialRegistered() { + DCHECK_EQ(kNotFinalized, group_); + DCHECK(!trial_registered_); + trial_registered_ = true; +} + void FieldTrial::SetGroupChoice(const std::string& group_name, int number) { group_ = number; if (group_name.empty()) @@ -434,9 +452,9 @@ FieldTrial* FieldTrialList::CreateFieldTrial( } const int kTotalProbability = 100; field_trial = new FieldTrial(name, kTotalProbability, group_name, 0); + FieldTrialList::Register(field_trial); // Force the trial, which will also finalize the group choice. field_trial->SetForced(); - FieldTrialList::Register(field_trial); return field_trial; } @@ -510,6 +528,7 @@ void FieldTrialList::Register(FieldTrial* trial) { AutoLock auto_lock(global_->lock_); DCHECK(!global_->PreLockedFind(trial->trial_name())); trial->AddRef(); + trial->SetTrialRegistered(); global_->registered_[trial->trial_name()] = trial; } diff --git a/chromium/base/metrics/field_trial.h b/chromium/base/metrics/field_trial.h index 5060a74cb5b..70ce2f9c45d 100644 --- a/chromium/base/metrics/field_trial.h +++ b/chromium/base/metrics/field_trial.h @@ -140,9 +140,6 @@ class BASE_EXPORT FieldTrial : public RefCounted<FieldTrial> { // is used as the group name. This causes a winner to be chosen if none was. const std::string& group_name(); - // Enable benchmarking sets field trials to a common setting. - static void EnableBenchmarking(); - // Set the field trial as forced, meaning that it was setup earlier than // the hard coded registration of the field trial to override it. // This allows the code that was hard coded to register the field trial to @@ -153,6 +150,25 @@ class BASE_EXPORT FieldTrial : public RefCounted<FieldTrial> { // be done from the UI thread. void SetForced(); + // Enable benchmarking sets field trials to a common setting. + static void EnableBenchmarking(); + + // Creates a FieldTrial object with the specified parameters, to be used for + // simulation of group assignment without actually affecting global field + // trial state in the running process. Group assignment will be done based on + // |entropy_value|, which must have a range of [0, 1). + // + // Note: Using this function will not register the field trial globally in the + // running process - for that, use FieldTrialList::FactoryGetFieldTrial(). + // + // The ownership of the returned FieldTrial is transfered to the caller which + // is responsible for deref'ing it (e.g. by using scoped_refptr<FieldTrial>). + static FieldTrial* CreateSimulatedFieldTrial( + const std::string& trial_name, + Probability total_probability, + const std::string& default_group_name, + double entropy_value); + private: // Allow tests to access our innards for testing purposes. FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, Registration); @@ -166,9 +182,6 @@ class BASE_EXPORT FieldTrial : public RefCounted<FieldTrial> { FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, ActiveGroupsNotFinalized); FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, Save); FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, DuplicateRestore); - FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, HashClientId); - FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, HashClientIdIsUniform); - FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, NameGroupIds); FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, SetForcedTurnFeatureOff); FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, SetForcedTurnFeatureOn); FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, SetForcedChangeDefault_Default); @@ -187,7 +200,7 @@ class BASE_EXPORT FieldTrial : public RefCounted<FieldTrial> { // Creates a field trial with the specified parameters. Group assignment will // be done based on |entropy_value|, which must have a range of [0, 1). - FieldTrial(const std::string& name, + FieldTrial(const std::string& trial_name, Probability total_probability, const std::string& default_group_name, double entropy_value); @@ -196,6 +209,10 @@ class BASE_EXPORT FieldTrial : public RefCounted<FieldTrial> { // Return the default group name of the FieldTrial. std::string default_group_name() const { return default_group_name_; } + // Marks this trial as having been registered with the FieldTrialList. Must be + // called no more than once and before any |group()| calls have occurred. + void SetTrialRegistered(); + // Sets the chosen group name and number. void SetGroupChoice(const std::string& group_name, int number); @@ -233,6 +250,7 @@ class BASE_EXPORT FieldTrial : public RefCounted<FieldTrial> { // Sum of the probabilities of all appended groups. Probability accumulated_group_probability_; + // The number that will be returned by the next AppendGroup() call. int next_group_number_; // The pseudo-randomly assigned group number. @@ -254,6 +272,10 @@ class BASE_EXPORT FieldTrial : public RefCounted<FieldTrial> { // Specifies whether the group choice has been reported to observers. bool group_reported_; + // Whether this trial is registered with the global FieldTrialList and thus + // should notify it when its group is queried. + bool trial_registered_; + // When benchmarking is enabled, field trials all revert to the 'default' // group. static bool enable_benchmarking_; diff --git a/chromium/base/metrics/field_trial_unittest.cc b/chromium/base/metrics/field_trial_unittest.cc index 10d3c3f9ed1..a77633e8f09 100644 --- a/chromium/base/metrics/field_trial_unittest.cc +++ b/chromium/base/metrics/field_trial_unittest.cc @@ -877,4 +877,47 @@ TEST_F(FieldTrialTest, DoesNotSurpassTotalProbability) { EXPECT_EQ("2", trial->group_name()); } +TEST_F(FieldTrialTest, CreateSimulatedFieldTrial) { + const char kTrialName[] = "CreateSimulatedFieldTrial"; + ASSERT_FALSE(FieldTrialList::TrialExists(kTrialName)); + + // Different cases to test, e.g. default vs. non default group being chosen. + struct { + double entropy_value; + const char* expected_group; + } test_cases[] = { + { 0.4, "A" }, + { 0.85, "B" }, + { 0.95, kDefaultGroupName }, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { + TestFieldTrialObserver observer; + scoped_refptr<FieldTrial> trial( + FieldTrial::CreateSimulatedFieldTrial(kTrialName, 100, kDefaultGroupName, + test_cases[i].entropy_value)); + trial->AppendGroup("A", 80); + trial->AppendGroup("B", 10); + EXPECT_EQ(test_cases[i].expected_group, trial->group_name()); + + // Field trial shouldn't have been registered with the list. + EXPECT_FALSE(FieldTrialList::TrialExists(kTrialName)); + EXPECT_EQ(0u, FieldTrialList::GetFieldTrialCount()); + + // Observer shouldn't have been notified. + RunLoop().RunUntilIdle(); + EXPECT_TRUE(observer.trial_name().empty()); + + // The trial shouldn't be in the active set of trials. + FieldTrial::ActiveGroups active_groups; + FieldTrialList::GetActiveFieldTrialGroups(&active_groups); + EXPECT_TRUE(active_groups.empty()); + + // The trial shouldn't be listed in the |StatesToString()| result. + std::string states; + FieldTrialList::StatesToString(&states); + EXPECT_TRUE(states.empty()); + } +} + } // namespace base diff --git a/chromium/base/metrics/histogram.h b/chromium/base/metrics/histogram.h index f2566caa3f1..9845362d98a 100644 --- a/chromium/base/metrics/histogram.h +++ b/chromium/base/metrics/histogram.h @@ -23,7 +23,7 @@ // agree exactly in type, bucket size and range. // For Histogram and LinearHistogram, the maximum for a declared range should -// always be larger (not equal) than minmal range. Zero and +// always be larger (not equal) than minimal range. Zero and // HistogramBase::kSampleType_MAX are implicitly added as first and last ranges, // so the smallest legal bucket_count is 3. However CustomHistogram can have // bucket count as 2 (when you give a custom ranges vector containing only 1 @@ -96,7 +96,7 @@ class Lock; // process. // The following code is generally what a thread-safe static pointer -// initializaion looks like for a histogram (after a macro is expanded). This +// initialization looks like for a histogram (after a macro is expanded). This // sample is an expansion (with comments) of the code for // HISTOGRAM_CUSTOM_COUNTS(). @@ -107,7 +107,7 @@ class Lock; static base::subtle::AtomicWord atomic_histogram_pointer = 0; // Acquire_Load() ensures that we acquire visibility to the pointed-to data - // in the histogrom. + // in the histogram. base::Histogram* histogram_pointer(reinterpret_cast<base::Histogram*>( base::subtle::Acquire_Load(&atomic_histogram_pointer))); @@ -421,7 +421,7 @@ class BASE_EXPORT Histogram : public HistogramBase { virtual int FindCorruption(const HistogramSamples& samples) const OVERRIDE; //---------------------------------------------------------------------------- - // Accessors for factory constuction, serialization and testing. + // Accessors for factory construction, serialization and testing. //---------------------------------------------------------------------------- Sample declared_min() const { return declared_min_; } Sample declared_max() const { return declared_max_; } @@ -604,7 +604,7 @@ class BASE_EXPORT LinearHistogram : public Histogram { static HistogramBase* DeserializeInfoImpl(PickleIterator* iter); // For some ranges, we store a printable description of a bucket range. - // If there is no desciption, then GetAsciiBucketRange() uses parent class + // If there is no description, then GetAsciiBucketRange() uses parent class // to provide a description. typedef std::map<Sample, std::string> BucketDescriptionMap; BucketDescriptionMap bucket_description_; diff --git a/chromium/base/metrics/histogram_base.cc b/chromium/base/metrics/histogram_base.cc index 5b46d2990c0..5c6e2d271bb 100644 --- a/chromium/base/metrics/histogram_base.cc +++ b/chromium/base/metrics/histogram_base.cc @@ -58,20 +58,6 @@ HistogramBase* DeserializeHistogramInfo(PickleIterator* iter) { } } -void DeserializeHistogramAndAddSamples(PickleIterator* iter) { - HistogramBase* histogram = DeserializeHistogramInfo(iter); - if (!histogram) - return; - - if (histogram->flags() & base::HistogramBase::kIPCSerializationSourceFlag) { - DVLOG(1) << "Single process mode, histogram observed and not copied: " - << histogram->histogram_name(); - return; - } - histogram->AddSamplesFromPickle(iter); -} - - const HistogramBase::Sample HistogramBase::kSampleType_MAX = INT_MAX; HistogramBase::HistogramBase(const std::string& name) diff --git a/chromium/base/metrics/histogram_base.h b/chromium/base/metrics/histogram_base.h index 46636477f3e..4248bd84a4c 100644 --- a/chromium/base/metrics/histogram_base.h +++ b/chromium/base/metrics/histogram_base.h @@ -6,6 +6,7 @@ #define BASE_METRICS_HISTOGRAM_BASE_H_ #include <string> +#include <vector> #include "base/atomicops.h" #include "base/base_export.h" @@ -43,10 +44,6 @@ std::string HistogramTypeToString(HistogramType type); BASE_EXPORT_PRIVATE HistogramBase* DeserializeHistogramInfo( PickleIterator* iter); -// Create or find existing histogram and add the samples from pickle. -// Silently returns when seeing any data problem in the pickle. -BASE_EXPORT void DeserializeHistogramAndAddSamples(PickleIterator* iter); - //////////////////////////////////////////////////////////////////////////////// class BASE_EXPORT HistogramBase { diff --git a/chromium/base/metrics/histogram_base_unittest.cc b/chromium/base/metrics/histogram_base_unittest.cc index 0e19d56f4c1..4a2963aa6c4 100644 --- a/chromium/base/metrics/histogram_base_unittest.cc +++ b/chromium/base/metrics/histogram_base_unittest.cc @@ -61,40 +61,6 @@ TEST_F(HistogramBaseTest, DeserializeHistogram) { EXPECT_EQ(HistogramBase::kUmaTargetedHistogramFlag, deserialized->flags()); } -TEST_F(HistogramBaseTest, DeserializeHistogramAndAddSamples) { - HistogramBase* histogram = Histogram::FactoryGet( - "TestHistogram", 1, 1000, 10, HistogramBase::kIPCSerializationSourceFlag); - histogram->Add(1); - histogram->Add(10); - histogram->Add(100); - histogram->Add(1000); - - Pickle pickle; - ASSERT_TRUE(histogram->SerializeInfo(&pickle)); - histogram->SnapshotSamples()->Serialize(&pickle); - - PickleIterator iter(pickle); - DeserializeHistogramAndAddSamples(&iter); - - // The histogram has kIPCSerializationSourceFlag. So samples will be ignored. - scoped_ptr<HistogramSamples> snapshot(histogram->SnapshotSamples()); - EXPECT_EQ(1, snapshot->GetCount(1)); - EXPECT_EQ(1, snapshot->GetCount(10)); - EXPECT_EQ(1, snapshot->GetCount(100)); - EXPECT_EQ(1, snapshot->GetCount(1000)); - - // Clear kIPCSerializationSourceFlag to emulate multi-process usage. - histogram->ClearFlags(HistogramBase::kIPCSerializationSourceFlag); - PickleIterator iter2(pickle); - DeserializeHistogramAndAddSamples(&iter2); - - scoped_ptr<HistogramSamples> snapshot2(histogram->SnapshotSamples()); - EXPECT_EQ(2, snapshot2->GetCount(1)); - EXPECT_EQ(2, snapshot2->GetCount(10)); - EXPECT_EQ(2, snapshot2->GetCount(100)); - EXPECT_EQ(2, snapshot2->GetCount(1000)); -} - TEST_F(HistogramBaseTest, DeserializeLinearHistogram) { HistogramBase* histogram = LinearHistogram::FactoryGet( "TestHistogram", 1, 1000, 10, diff --git a/chromium/base/metrics/histogram_delta_serialization.cc b/chromium/base/metrics/histogram_delta_serialization.cc new file mode 100644 index 00000000000..924916db76e --- /dev/null +++ b/chromium/base/metrics/histogram_delta_serialization.cc @@ -0,0 +1,111 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/metrics/histogram_delta_serialization.h" + +#include "base/logging.h" +#include "base/metrics/histogram_base.h" +#include "base/metrics/histogram_snapshot_manager.h" +#include "base/pickle.h" +#include "base/safe_numerics.h" +#include "base/values.h" + +namespace base { + +namespace { + +// Create or find existing histogram and add the samples from pickle. +// Silently returns when seeing any data problem in the pickle. +void DeserializeHistogramAndAddSamples(PickleIterator* iter) { + HistogramBase* histogram = DeserializeHistogramInfo(iter); + if (!histogram) + return; + + if (histogram->flags() & HistogramBase::kIPCSerializationSourceFlag) { + DVLOG(1) << "Single process mode, histogram observed and not copied: " + << histogram->histogram_name(); + return; + } + histogram->AddSamplesFromPickle(iter); +} + +} // namespace + +HistogramDeltaSerialization::HistogramDeltaSerialization( + const std::string& caller_name) + : histogram_snapshot_manager_(this), + serialized_deltas_(NULL) { + inconsistencies_histogram_ = + LinearHistogram::FactoryGet( + "Histogram.Inconsistencies" + caller_name, 1, + HistogramBase::NEVER_EXCEEDED_VALUE, + HistogramBase::NEVER_EXCEEDED_VALUE + 1, + HistogramBase::kUmaTargetedHistogramFlag); + + inconsistencies_unique_histogram_ = + LinearHistogram::FactoryGet( + "Histogram.Inconsistencies" + caller_name + "Unique", 1, + HistogramBase::NEVER_EXCEEDED_VALUE, + HistogramBase::NEVER_EXCEEDED_VALUE + 1, + HistogramBase::kUmaTargetedHistogramFlag); + + inconsistent_snapshot_histogram_ = + Histogram::FactoryGet( + "Histogram.InconsistentSnapshot" + caller_name, 1, 1000000, 50, + HistogramBase::kUmaTargetedHistogramFlag); +} + +HistogramDeltaSerialization::~HistogramDeltaSerialization() { +} + +void HistogramDeltaSerialization::PrepareAndSerializeDeltas( + std::vector<std::string>* serialized_deltas) { + serialized_deltas_ = serialized_deltas; + // Note: Before serializing, we set the kIPCSerializationSourceFlag for all + // the histograms, so that the receiving process can distinguish them from the + // local histograms. + histogram_snapshot_manager_.PrepareDeltas( + Histogram::kIPCSerializationSourceFlag, false); + serialized_deltas_ = NULL; +} + +// static +void HistogramDeltaSerialization::DeserializeAndAddSamples( + const std::vector<std::string>& serialized_deltas) { + for (std::vector<std::string>::const_iterator it = serialized_deltas.begin(); + it != serialized_deltas.end(); ++it) { + Pickle pickle(it->data(), checked_numeric_cast<int>(it->size())); + PickleIterator iter(pickle); + DeserializeHistogramAndAddSamples(&iter); + } +} + +void HistogramDeltaSerialization::RecordDelta( + const HistogramBase& histogram, + const HistogramSamples& snapshot) { + DCHECK_NE(0, snapshot.TotalCount()); + + Pickle pickle; + histogram.SerializeInfo(&pickle); + snapshot.Serialize(&pickle); + serialized_deltas_->push_back( + std::string(static_cast<const char*>(pickle.data()), pickle.size())); +} + +void HistogramDeltaSerialization::InconsistencyDetected( + HistogramBase::Inconsistency problem) { + inconsistencies_histogram_->Add(problem); +} + +void HistogramDeltaSerialization::UniqueInconsistencyDetected( + HistogramBase::Inconsistency problem) { + inconsistencies_unique_histogram_->Add(problem); +} + +void HistogramDeltaSerialization::InconsistencyDetectedInLoggedCount( + int amount) { + inconsistent_snapshot_histogram_->Add(std::abs(amount)); +} + +} // namespace base diff --git a/chromium/base/metrics/histogram_delta_serialization.h b/chromium/base/metrics/histogram_delta_serialization.h new file mode 100644 index 00000000000..ccadb12894f --- /dev/null +++ b/chromium/base/metrics/histogram_delta_serialization.h @@ -0,0 +1,65 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_METRICS_HISTOGRAM_DELTA_SERIALIZATION_H_ +#define BASE_METRICS_HISTOGRAM_DELTA_SERIALIZATION_H_ + +#include <string> +#include <vector> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/metrics/histogram_flattener.h" +#include "base/metrics/histogram_snapshot_manager.h" + +namespace base { + +class HistogramBase; + +// Serializes and restores histograms deltas. +class BASE_EXPORT HistogramDeltaSerialization : public HistogramFlattener { + public: + // |caller_name| is string used in histograms for counting inconsistencies. + explicit HistogramDeltaSerialization(const std::string& caller_name); + virtual ~HistogramDeltaSerialization(); + + // Computes deltas in histogram bucket counts relative to the previous call to + // this method. Stores the deltas in serialized form into |serialized_deltas|. + // If |serialized_deltas| is NULL, no data is serialized, though the next call + // will compute the deltas relative to this one. + void PrepareAndSerializeDeltas(std::vector<std::string>* serialized_deltas); + + // Deserialize deltas and add samples to corresponding histograms, creating + // them if necessary. Silently ignores errors in |serialized_deltas|. + static void DeserializeAndAddSamples( + const std::vector<std::string>& serialized_deltas); + + private: + // HistogramFlattener implementation. + virtual void RecordDelta(const HistogramBase& histogram, + const HistogramSamples& snapshot) OVERRIDE; + virtual void InconsistencyDetected( + HistogramBase::Inconsistency problem) OVERRIDE; + virtual void UniqueInconsistencyDetected( + HistogramBase::Inconsistency problem) OVERRIDE; + virtual void InconsistencyDetectedInLoggedCount(int amount) OVERRIDE; + + // Calculates deltas in histogram counters. + HistogramSnapshotManager histogram_snapshot_manager_; + + // Output buffer for serialized deltas. + std::vector<std::string>* serialized_deltas_; + + // Histograms to count inconsistencies in snapshots. + HistogramBase* inconsistencies_histogram_; + HistogramBase* inconsistencies_unique_histogram_; + HistogramBase* inconsistent_snapshot_histogram_; + + DISALLOW_COPY_AND_ASSIGN(HistogramDeltaSerialization); +}; + +} // namespace base + +#endif // BASE_METRICS_HISTOGRAM_DELTA_SERIALIZATION_H_ diff --git a/chromium/base/metrics/histogram_delta_serialization_unittest.cc b/chromium/base/metrics/histogram_delta_serialization_unittest.cc new file mode 100644 index 00000000000..b53520c5098 --- /dev/null +++ b/chromium/base/metrics/histogram_delta_serialization_unittest.cc @@ -0,0 +1,54 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/metrics/histogram_delta_serialization.h" + +#include <vector> + +#include "base/metrics/histogram.h" +#include "base/metrics/histogram_base.h" +#include "base/metrics/statistics_recorder.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +TEST(HistogramDeltaSerializationTest, DeserializeHistogramAndAddSamples) { + StatisticsRecorder statistic_recorder; + HistogramDeltaSerialization serializer("HistogramDeltaSerializationTest"); + std::vector<std::string> deltas; + // Nothing was changed yet. + serializer.PrepareAndSerializeDeltas(&deltas); + EXPECT_TRUE(deltas.empty()); + + HistogramBase* histogram = Histogram::FactoryGet( + "TestHistogram", 1, 1000, 10, HistogramBase::kIPCSerializationSourceFlag); + histogram->Add(1); + histogram->Add(10); + histogram->Add(100); + histogram->Add(1000); + + serializer.PrepareAndSerializeDeltas(&deltas); + EXPECT_FALSE(deltas.empty()); + + HistogramDeltaSerialization::DeserializeAndAddSamples(deltas); + + // The histogram has kIPCSerializationSourceFlag. So samples will be ignored. + scoped_ptr<HistogramSamples> snapshot(histogram->SnapshotSamples()); + EXPECT_EQ(1, snapshot->GetCount(1)); + EXPECT_EQ(1, snapshot->GetCount(10)); + EXPECT_EQ(1, snapshot->GetCount(100)); + EXPECT_EQ(1, snapshot->GetCount(1000)); + + // Clear kIPCSerializationSourceFlag to emulate multi-process usage. + histogram->ClearFlags(HistogramBase::kIPCSerializationSourceFlag); + HistogramDeltaSerialization::DeserializeAndAddSamples(deltas); + + scoped_ptr<HistogramSamples> snapshot2(histogram->SnapshotSamples()); + EXPECT_EQ(2, snapshot2->GetCount(1)); + EXPECT_EQ(2, snapshot2->GetCount(10)); + EXPECT_EQ(2, snapshot2->GetCount(100)); + EXPECT_EQ(2, snapshot2->GetCount(1000)); +} + +} // namespace base diff --git a/chromium/base/metrics/histogram_snapshot_manager.cc b/chromium/base/metrics/histogram_snapshot_manager.cc index 2301819ad30..cb594bd96d9 100644 --- a/chromium/base/metrics/histogram_snapshot_manager.cc +++ b/chromium/base/metrics/histogram_snapshot_manager.cc @@ -94,7 +94,7 @@ void HistogramSnapshotManager::PrepareDelta(const HistogramBase& histogram) { to_log = snapshot.get(); } - if (to_log->redundant_count() > 0) + if (to_log->TotalCount() > 0) histogram_flattener_->RecordDelta(histogram, *to_log); } diff --git a/chromium/base/metrics/statistics_recorder.cc b/chromium/base/metrics/statistics_recorder.cc index f23c81054cf..62617c5d804 100644 --- a/chromium/base/metrics/statistics_recorder.cc +++ b/chromium/base/metrics/statistics_recorder.cc @@ -6,11 +6,13 @@ #include "base/at_exit.h" #include "base/debug/leak_annotations.h" +#include "base/json/string_escape.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/metrics/histogram.h" #include "base/strings/stringprintf.h" #include "base/synchronization/lock.h" +#include "base/values.h" using std::list; using std::string; @@ -163,6 +165,36 @@ void StatisticsRecorder::WriteGraph(const std::string& query, } // static +std::string StatisticsRecorder::ToJSON(const std::string& query) { + if (!IsActive()) + return std::string(); + + std::string output("{"); + if (!query.empty()) { + output += "\"query\":"; + EscapeJSONString(query, true, &output); + output += ","; + } + + Histograms snapshot; + GetSnapshot(query, &snapshot); + output += "\"histograms\":["; + bool first_histogram = true; + for (Histograms::const_iterator it = snapshot.begin(); it != snapshot.end(); + ++it) { + if (first_histogram) + first_histogram = false; + else + output += ","; + std::string json; + (*it)->WriteJSON(&json); + output += json; + } + output += "]}"; + return output; +} + +// static void StatisticsRecorder::GetHistograms(Histograms* output) { if (lock_ == NULL) return; diff --git a/chromium/base/metrics/statistics_recorder.h b/chromium/base/metrics/statistics_recorder.h index 9a552253243..0716e80572d 100644 --- a/chromium/base/metrics/statistics_recorder.h +++ b/chromium/base/metrics/statistics_recorder.h @@ -49,12 +49,16 @@ class BASE_EXPORT StatisticsRecorder { static const BucketRanges* RegisterOrDeleteDuplicateRanges( const BucketRanges* ranges); - // Methods for printing histograms. Only histograms which have query as - // a substring are written to output (an empty string will process all - // registered histograms). + // Methods for appending histogram data to a string. Only histograms which + // have |query| as a substring are written to |output| (an empty string will + // process all registered histograms). static void WriteHTMLGraph(const std::string& query, std::string* output); static void WriteGraph(const std::string& query, std::string* output); + // Returns the histograms with |query| as a substring as JSON text (an empty + // |query| will process all registered histograms). + static std::string ToJSON(const std::string& query); + // Method for extracting histograms which were marked for use by UMA. static void GetHistograms(Histograms* output); @@ -85,6 +89,8 @@ class BASE_EXPORT StatisticsRecorder { friend class HistogramTest; friend class SparseHistogramTest; friend class StatisticsRecorderTest; + FRIEND_TEST_ALL_PREFIXES(HistogramDeltaSerializationTest, + DeserializeHistogramAndAddSamples); // The constructor just initializes static members. Usually client code should // use Initialize to do this. But in test code, you can friend this class and diff --git a/chromium/base/metrics/statistics_recorder_unittest.cc b/chromium/base/metrics/statistics_recorder_unittest.cc index 22504ddfdda..906e6422549 100644 --- a/chromium/base/metrics/statistics_recorder_unittest.cc +++ b/chromium/base/metrics/statistics_recorder_unittest.cc @@ -4,9 +4,11 @@ #include <vector> +#include "base/json/json_reader.h" #include "base/memory/scoped_ptr.h" #include "base/metrics/histogram.h" #include "base/metrics/statistics_recorder.h" +#include "base/values.h" #include "testing/gtest/include/gtest/gtest.h" namespace base { @@ -263,4 +265,64 @@ TEST_F(StatisticsRecorderTest, BucketRangesSharing) { EXPECT_EQ(2u, ranges.size()); } +TEST_F(StatisticsRecorderTest, ToJSON) { + HISTOGRAM_COUNTS("TestHistogram1", 30); + HISTOGRAM_COUNTS("TestHistogram1", 40); + HISTOGRAM_COUNTS("TestHistogram2", 30); + HISTOGRAM_COUNTS("TestHistogram2", 40); + + std::string json(StatisticsRecorder::ToJSON(std::string())); + + // Check for valid JSON. + scoped_ptr<Value> root; + root.reset(JSONReader::Read(json)); + ASSERT_TRUE(root.get()); + + DictionaryValue* root_dict = NULL; + ASSERT_TRUE(root->GetAsDictionary(&root_dict)); + + // No query should be set. + ASSERT_FALSE(root_dict->HasKey("query")); + + ListValue* histogram_list = NULL; + ASSERT_TRUE(root_dict->GetList("histograms", &histogram_list)); + ASSERT_EQ(2u, histogram_list->GetSize()); + + // Examine the first histogram. + DictionaryValue* histogram_dict = NULL; + ASSERT_TRUE(histogram_list->GetDictionary(0, &histogram_dict)); + + int sample_count; + ASSERT_TRUE(histogram_dict->GetInteger("count", &sample_count)); + EXPECT_EQ(2, sample_count); + + // Test the query filter. + std::string query("TestHistogram2"); + json = StatisticsRecorder::ToJSON(query); + + root.reset(JSONReader::Read(json)); + ASSERT_TRUE(root.get()); + ASSERT_TRUE(root->GetAsDictionary(&root_dict)); + + std::string query_value; + ASSERT_TRUE(root_dict->GetString("query", &query_value)); + EXPECT_EQ(query, query_value); + + ASSERT_TRUE(root_dict->GetList("histograms", &histogram_list)); + ASSERT_EQ(1u, histogram_list->GetSize()); + + ASSERT_TRUE(histogram_list->GetDictionary(0, &histogram_dict)); + + std::string histogram_name; + ASSERT_TRUE(histogram_dict->GetString("name", &histogram_name)); + EXPECT_EQ("TestHistogram2", histogram_name); + + json.clear(); + UninitializeStatisticsRecorder(); + + // No data should be returned. + json = StatisticsRecorder::ToJSON(query); + EXPECT_TRUE(json.empty()); +} + } // namespace base diff --git a/chromium/base/metrics/stats_table.cc b/chromium/base/metrics/stats_table.cc index 403e28c9297..716b7cfc91d 100644 --- a/chromium/base/metrics/stats_table.cc +++ b/chromium/base/metrics/stats_table.cc @@ -15,7 +15,9 @@ #include "base/threading/thread_local_storage.h" #if defined(OS_POSIX) +#include "base/posix/global_descriptors.h" #include "errno.h" +#include "ipc/ipc_descriptors.h" #endif namespace base { @@ -88,10 +90,10 @@ inline int AlignedSize(int size) { } // namespace -// The StatsTable::Private maintains convenience pointers into the +// The StatsTable::Internal maintains convenience pointers into the // shared memory segment. Use this class to keep the data structure // clean and accessible. -class StatsTable::Private { +class StatsTable::Internal { public: // Various header information contained in the memory mapped segment. struct TableHeader { @@ -101,12 +103,14 @@ class StatsTable::Private { int max_threads; }; - // Construct a new Private based on expected size parameters, or + // Construct a new Internal based on expected size parameters, or // return NULL on failure. - static Private* New(const std::string& name, int size, - int max_threads, int max_counters); + static Internal* New(const std::string& name, + int size, + int max_threads, + int max_counters); - SharedMemory* shared_memory() { return &shared_memory_; } + SharedMemory* shared_memory() { return shared_memory_.get(); } // Accessors for our header pointers TableHeader* table_header() const { return table_header_; } @@ -136,8 +140,9 @@ class StatsTable::Private { private: // Constructor is private because you should use New() instead. - Private() - : table_header_(NULL), + explicit Internal(SharedMemory* shared_memory) + : shared_memory_(shared_memory), + table_header_(NULL), thread_names_table_(NULL), thread_tid_table_(NULL), thread_pid_table_(NULL), @@ -145,6 +150,10 @@ class StatsTable::Private { data_table_(NULL) { } + // Create or open the SharedMemory used by the stats table. + static SharedMemory* CreateSharedMemory(const std::string& name, + int size); + // Initializes the table on first access. Sets header values // appropriately and zeroes all counters. void InitializeTable(void* memory, int size, int max_counters, @@ -153,41 +162,68 @@ class StatsTable::Private { // Initializes our in-memory pointers into a pre-created StatsTable. void ComputeMappedPointers(void* memory); - SharedMemory shared_memory_; + scoped_ptr<SharedMemory> shared_memory_; TableHeader* table_header_; char* thread_names_table_; PlatformThreadId* thread_tid_table_; int* thread_pid_table_; char* counter_names_table_; int* data_table_; + + DISALLOW_COPY_AND_ASSIGN(Internal); }; // static -StatsTable::Private* StatsTable::Private::New(const std::string& name, - int size, - int max_threads, - int max_counters) { - scoped_ptr<Private> priv(new Private()); - if (!priv->shared_memory_.CreateNamed(name, true, size)) +StatsTable::Internal* StatsTable::Internal::New(const std::string& name, + int size, + int max_threads, + int max_counters) { + scoped_ptr<SharedMemory> shared_memory(CreateSharedMemory(name, size)); + if (!shared_memory.get()) return NULL; - if (!priv->shared_memory_.Map(size)) + if (!shared_memory->Map(size)) return NULL; - void* memory = priv->shared_memory_.memory(); + void* memory = shared_memory->memory(); + scoped_ptr<Internal> internal(new Internal(shared_memory.release())); TableHeader* header = static_cast<TableHeader*>(memory); // If the version does not match, then assume the table needs // to be initialized. if (header->version != kTableVersion) - priv->InitializeTable(memory, size, max_counters, max_threads); + internal->InitializeTable(memory, size, max_counters, max_threads); // We have a valid table, so compute our pointers. - priv->ComputeMappedPointers(memory); + internal->ComputeMappedPointers(memory); + + return internal.release(); +} - return priv.release(); +// static +SharedMemory* StatsTable::Internal::CreateSharedMemory(const std::string& name, + int size) { +#if defined(OS_POSIX) + GlobalDescriptors* global_descriptors = GlobalDescriptors::GetInstance(); + if (global_descriptors->MaybeGet(kStatsTableSharedMemFd) != -1) { + // Open the shared memory file descriptor passed by the browser process. + FileDescriptor file_descriptor( + global_descriptors->Get(kStatsTableSharedMemFd), false); + return new SharedMemory(file_descriptor, false); + } + // Otherwise we need to create it. + scoped_ptr<SharedMemory> shared_memory(new SharedMemory()); + if (!shared_memory->CreateAnonymous(size)) + return NULL; + return shared_memory.release(); +#elif defined(OS_WIN) + scoped_ptr<SharedMemory> shared_memory(new SharedMemory()); + if (!shared_memory->CreateNamed(name, true, size)) + return NULL; + return shared_memory.release(); +#endif } -void StatsTable::Private::InitializeTable(void* memory, int size, +void StatsTable::Internal::InitializeTable(void* memory, int size, int max_counters, int max_threads) { // Zero everything. @@ -201,7 +237,7 @@ void StatsTable::Private::InitializeTable(void* memory, int size, header->max_threads = max_threads; } -void StatsTable::Private::ComputeMappedPointers(void* memory) { +void StatsTable::Internal::ComputeMappedPointers(void* memory) { char* data = static_cast<char*>(memory); int offset = 0; @@ -252,19 +288,19 @@ StatsTable* global_table = NULL; StatsTable::StatsTable(const std::string& name, int max_threads, int max_counters) - : impl_(NULL), + : internal_(NULL), tls_index_(SlotReturnFunction) { int table_size = - AlignedSize(sizeof(Private::TableHeader)) + + AlignedSize(sizeof(Internal::TableHeader)) + AlignedSize((max_counters * sizeof(char) * kMaxCounterNameLength)) + AlignedSize((max_threads * sizeof(char) * kMaxThreadNameLength)) + AlignedSize(max_threads * sizeof(int)) + AlignedSize(max_threads * sizeof(int)) + AlignedSize((sizeof(int) * (max_counters * max_threads))); - impl_ = Private::New(name, table_size, max_threads, max_counters); + internal_ = Internal::New(name, table_size, max_threads, max_counters); - if (!impl_) + if (!internal_) DPLOG(ERROR) << "StatsTable did not initialize"; } @@ -278,7 +314,7 @@ StatsTable::~StatsTable() { tls_index_.Free(); // Cleanup our shared memory. - delete impl_; + delete internal_; // If we are the global table, unregister ourselves. if (global_table == this) @@ -302,14 +338,14 @@ int StatsTable::GetSlot() const { int StatsTable::RegisterThread(const std::string& name) { int slot = 0; - if (!impl_) + if (!internal_) return 0; // Registering a thread requires that we lock the shared memory // so that two threads don't grab the same slot. Fortunately, // thread creation shouldn't happen in inner loops. { - SharedMemoryAutoLock lock(impl_->shared_memory()); + SharedMemoryAutoLock lock(internal_->shared_memory()); slot = FindEmptyThread(); if (!slot) { return 0; @@ -319,10 +355,10 @@ int StatsTable::RegisterThread(const std::string& name) { std::string thread_name = name; if (name.empty()) thread_name = kUnknownName; - strlcpy(impl_->thread_name(slot), thread_name.c_str(), + strlcpy(internal_->thread_name(slot), thread_name.c_str(), kMaxThreadNameLength); - *(impl_->thread_tid(slot)) = PlatformThread::CurrentId(); - *(impl_->thread_pid(slot)) = GetCurrentProcId(); + *(internal_->thread_tid(slot)) = PlatformThread::CurrentId(); + *(internal_->thread_pid(slot)) = GetCurrentProcId(); } // Set our thread local storage. @@ -334,14 +370,14 @@ int StatsTable::RegisterThread(const std::string& name) { } int StatsTable::CountThreadsRegistered() const { - if (!impl_) + if (!internal_) return 0; // Loop through the shared memory and count the threads that are active. // We intentionally do not lock the table during the operation. int count = 0; - for (int index = 1; index <= impl_->max_threads(); index++) { - char* name = impl_->thread_name(index); + for (int index = 1; index <= internal_->max_threads(); index++) { + char* name = internal_->thread_name(index); if (*name != '\0') count++; } @@ -352,7 +388,7 @@ int StatsTable::FindCounter(const std::string& name) { // Note: the API returns counters numbered from 1..N, although // internally, the array is 0..N-1. This is so that we can return // zero as "not found". - if (!impl_) + if (!internal_) return 0; // Create a scope for our auto-lock. @@ -371,20 +407,20 @@ int StatsTable::FindCounter(const std::string& name) { } int* StatsTable::GetLocation(int counter_id, int slot_id) const { - if (!impl_) + if (!internal_) return NULL; - if (slot_id > impl_->max_threads()) + if (slot_id > internal_->max_threads()) return NULL; - int* row = impl_->row(counter_id); + int* row = internal_->row(counter_id); return &(row[slot_id-1]); } const char* StatsTable::GetRowName(int index) const { - if (!impl_) + if (!internal_) return NULL; - return impl_->counter_name(index); + return internal_->counter_name(index); } int StatsTable::GetRowValue(int index) const { @@ -392,13 +428,13 @@ int StatsTable::GetRowValue(int index) const { } int StatsTable::GetRowValue(int index, int pid) const { - if (!impl_) + if (!internal_) return 0; int rv = 0; - int* row = impl_->row(index); - for (int slot_id = 1; slot_id <= impl_->max_threads(); slot_id++) { - if (pid == 0 || *impl_->thread_pid(slot_id) == pid) + int* row = internal_->row(index); + for (int slot_id = 1; slot_id <= internal_->max_threads(); slot_id++) { + if (pid == 0 || *internal_->thread_pid(slot_id) == pid) rv += row[slot_id-1]; } return rv; @@ -409,7 +445,7 @@ int StatsTable::GetCounterValue(const std::string& name) { } int StatsTable::GetCounterValue(const std::string& name, int pid) { - if (!impl_) + if (!internal_) return 0; int row = FindCounter(name); @@ -419,15 +455,15 @@ int StatsTable::GetCounterValue(const std::string& name, int pid) { } int StatsTable::GetMaxCounters() const { - if (!impl_) + if (!internal_) return 0; - return impl_->max_counters(); + return internal_->max_counters(); } int StatsTable::GetMaxThreads() const { - if (!impl_) + if (!internal_) return 0; - return impl_->max_threads(); + return internal_->max_threads(); } int* StatsTable::FindLocation(const char* name) { @@ -457,10 +493,10 @@ void StatsTable::UnregisterThread() { void StatsTable::UnregisterThread(TLSData* data) { if (!data) return; - DCHECK(impl_); + DCHECK(internal_); // Mark the slot free by zeroing out the thread name. - char* name = impl_->thread_name(data->slot); + char* name = internal_->thread_name(data->slot); *name = '\0'; // Remove the calling thread's TLS so that it cannot use the slot. @@ -488,16 +524,16 @@ int StatsTable::FindEmptyThread() const { // in TLS, which is always initialized to zero, not -1. If 0 were // returned as a valid slot number, it would be confused with the // uninitialized state. - if (!impl_) + if (!internal_) return 0; int index = 1; - for (; index <= impl_->max_threads(); index++) { - char* name = impl_->thread_name(index); + for (; index <= internal_->max_threads(); index++) { + char* name = internal_->thread_name(index); if (!*name) break; } - if (index > impl_->max_threads()) + if (index > internal_->max_threads()) return 0; // The table is full. return index; } @@ -510,12 +546,12 @@ int StatsTable::FindCounterOrEmptyRow(const std::string& name) const { // There isn't much reason for this other than to be consistent // with the way we track columns for thread slots. (See comments // in FindEmptyThread for why it is done this way). - if (!impl_) + if (!internal_) return 0; int free_slot = 0; - for (int index = 1; index <= impl_->max_counters(); index++) { - char* row_name = impl_->counter_name(index); + for (int index = 1; index <= internal_->max_counters(); index++) { + char* row_name = internal_->counter_name(index); if (!*row_name && !free_slot) free_slot = index; // save that we found a free slot else if (!strncmp(row_name, name.c_str(), kMaxCounterNameLength)) @@ -525,14 +561,14 @@ int StatsTable::FindCounterOrEmptyRow(const std::string& name) const { } int StatsTable::AddCounter(const std::string& name) { - if (!impl_) + if (!internal_) return 0; int counter_id = 0; { // To add a counter to the shared memory, we need the // shared memory lock. - SharedMemoryAutoLock lock(impl_->shared_memory()); + SharedMemoryAutoLock lock(internal_->shared_memory()); // We have space, so create a new counter. counter_id = FindCounterOrEmptyRow(name); @@ -542,7 +578,7 @@ int StatsTable::AddCounter(const std::string& name) { std::string counter_name = name; if (name.empty()) counter_name = kUnknownName; - strlcpy(impl_->counter_name(counter_id), counter_name.c_str(), + strlcpy(internal_->counter_name(counter_id), counter_name.c_str(), kMaxCounterNameLength); } @@ -565,4 +601,12 @@ StatsTable::TLSData* StatsTable::GetTLSData() const { return data; } +#if defined(OS_POSIX) +SharedMemoryHandle StatsTable::GetSharedMemoryHandle() const { + if (!internal_) + return SharedMemory::NULLHandle(); + return internal_->shared_memory()->handle(); +} +#endif + } // namespace base diff --git a/chromium/base/metrics/stats_table.h b/chromium/base/metrics/stats_table.h index 153af38c928..49ba79f88ba 100644 --- a/chromium/base/metrics/stats_table.h +++ b/chromium/base/metrics/stats_table.h @@ -25,6 +25,7 @@ #include "base/base_export.h" #include "base/basictypes.h" #include "base/containers/hash_tables.h" +#include "base/memory/shared_memory.h" #include "base/synchronization/lock.h" #include "base/threading/thread_local_storage.h" @@ -116,6 +117,11 @@ class BASE_EXPORT StatsTable { // The maxinum number of threads/columns in the table. int GetMaxThreads() const; +#if defined(OS_POSIX) + // Get the underlying shared memory handle for the table. + base::SharedMemoryHandle GetSharedMemoryHandle() const; +#endif + // The maximum length (in characters) of a Thread's name including // null terminator, as stored in the shared memory. static const int kMaxThreadNameLength = 32; @@ -130,7 +136,7 @@ class BASE_EXPORT StatsTable { static int* FindLocation(const char *name); private: - class Private; + class Internal; struct TLSData; typedef hash_map<std::string, int> CountersMap; @@ -172,7 +178,7 @@ class BASE_EXPORT StatsTable { // initialized. TLSData* GetTLSData() const; - Private* impl_; + Internal* internal_; // The counters_lock_ protects the counters_ hash table. base::Lock counters_lock_; diff --git a/chromium/base/move.h b/chromium/base/move.h index d2cd3df4f7d..1c67155be1c 100644 --- a/chromium/base/move.h +++ b/chromium/base/move.h @@ -136,6 +136,16 @@ // choose the one that adheres to the standard. // // +// WHY HAVE typedef void MoveOnlyTypeForCPP03 +// +// Callback<>/Bind() needs to understand movable-but-not-copyable semantics +// to call .Pass() appropriately when it is expected to transfer the value. +// The cryptic typedef MoveOnlyTypeForCPP03 is added to make this check +// easy and automatic in helper templates for Callback<>/Bind(). +// See IsMoveOnlyType template and its usage in base/callback_internal.h +// for more details. +// +// // COMPARED TO C++11 // // In C++11, you would implement this functionality using an r-value reference @@ -202,6 +212,7 @@ public: \ operator rvalue_type() { return rvalue_type(this); } \ type Pass() { return type(rvalue_type(this)); } \ + typedef void MoveOnlyTypeForCPP03; \ private: #endif // BASE_MOVE_H_ diff --git a/chromium/base/nix/mime_util_xdg.cc b/chromium/base/nix/mime_util_xdg.cc index 1a41394fc15..dd2c7eb663c 100644 --- a/chromium/base/nix/mime_util_xdg.cc +++ b/chromium/base/nix/mime_util_xdg.cc @@ -32,13 +32,12 @@ class IconTheme; // None of the XDG stuff is thread-safe, so serialize all access under // this lock. -base::LazyInstance<base::Lock>::Leaky - g_mime_util_xdg_lock = LAZY_INSTANCE_INITIALIZER; +LazyInstance<Lock>::Leaky g_mime_util_xdg_lock = LAZY_INSTANCE_INITIALIZER; class MimeUtilConstants { public: typedef std::map<std::string, IconTheme*> IconThemeMap; - typedef std::map<FilePath, base::Time> IconDirMtimeMap; + typedef std::map<FilePath, Time> IconDirMtimeMap; typedef std::vector<std::string> IconFormats; // Specified by XDG icon theme specs. @@ -62,7 +61,7 @@ class MimeUtilConstants { // The default theme. IconTheme* default_themes_[kDefaultThemeNum]; - base::TimeTicks last_check_time_; + TimeTicks last_check_time_; // The current icon theme, usually set through GTK theme integration. std::string icon_theme_name_; @@ -159,7 +158,7 @@ class IconTheme { IconTheme::IconTheme(const std::string& name) : index_theme_loaded_(false) { - base::ThreadRestrictions::AssertIOAllowed(); + ThreadRestrictions::AssertIOAllowed(); // Iterate on all icon directories to find directories of the specified // theme and load the first encountered index.theme. MimeUtilConstants::IconDirMtimeMap::iterator iter; @@ -256,7 +255,7 @@ FilePath IconTheme::GetIconPathUnderSubdir(const std::string& icon_name, } bool IconTheme::LoadIndexTheme(const FilePath& file) { - FILE* fp = file_util::OpenFile(file, "r"); + FILE* fp = base::OpenFile(file, "r"); SubDirInfo* current_info = NULL; if (!fp) return false; @@ -281,7 +280,7 @@ bool IconTheme::LoadIndexTheme(const FilePath& file) { std::string key, value; std::vector<std::string> r; - base::SplitStringDontTrim(entry, '=', &r); + SplitStringDontTrim(entry, '=', &r); if (r.size() < 2) continue; @@ -317,7 +316,7 @@ bool IconTheme::LoadIndexTheme(const FilePath& file) { } } - file_util::CloseFile(fp); + base::CloseFile(fp); return info_array_.get() != NULL; } @@ -385,12 +384,11 @@ bool IconTheme::SetDirectories(const std::string& dirs) { return true; } -bool CheckDirExistsAndGetMtime(const FilePath& dir, - base::Time* last_modified) { +bool CheckDirExistsAndGetMtime(const FilePath& dir, Time* last_modified) { if (!DirectoryExists(dir)) return false; - base::PlatformFileInfo file_info; - if (!file_util::GetFileInfo(dir, &file_info)) + PlatformFileInfo file_info; + if (!GetFileInfo(dir, &file_info)) return false; *last_modified = file_info.last_modified; return true; @@ -398,7 +396,7 @@ bool CheckDirExistsAndGetMtime(const FilePath& dir, // Make sure |dir| exists and add it to the list of icon directories. void TryAddIconDir(const FilePath& dir) { - base::Time last_modified; + Time last_modified; if (!CheckDirExistsAndGetMtime(dir, &last_modified)) return; MimeUtilConstants::GetInstance()->icon_dirs_[dir] = last_modified; @@ -414,7 +412,7 @@ void AddXDGDataDir(const FilePath& dir) { // Add all the xdg icon directories. void InitIconDir() { - FilePath home = file_util::GetHomeDir(); + FilePath home = GetHomeDir(); if (!home.empty()) { FilePath legacy_data_dir(home); legacy_data_dir = legacy_data_dir.AppendASCII(".icons"); @@ -449,15 +447,15 @@ void InitIconDir() { void EnsureUpdated() { MimeUtilConstants* constants = MimeUtilConstants::GetInstance(); if (constants->last_check_time_.is_null()) { - constants->last_check_time_ = base::TimeTicks::Now(); + constants->last_check_time_ = TimeTicks::Now(); InitIconDir(); return; } // Per xdg theme spec, we should check the icon directories every so often // for newly added icons. - base::TimeDelta time_since_last_check = - base::TimeTicks::Now() - constants->last_check_time_; + TimeDelta time_since_last_check = + TimeTicks::Now() - constants->last_check_time_; if (time_since_last_check.InSeconds() > constants->kUpdateIntervalInSeconds) { constants->last_check_time_ += time_since_last_check; @@ -465,7 +463,7 @@ void EnsureUpdated() { MimeUtilConstants::IconDirMtimeMap* icon_dirs = &constants->icon_dirs_; MimeUtilConstants::IconDirMtimeMap::iterator iter; for (iter = icon_dirs->begin(); iter != icon_dirs->end(); ++iter) { - base::Time last_modified; + Time last_modified; if (!CheckDirExistsAndGetMtime(iter->first, &last_modified) || last_modified != iter->second) { rescan_icon_dirs = true; @@ -502,7 +500,7 @@ void InitDefaultThemes() { IconTheme** default_themes = MimeUtilConstants::GetInstance()->default_themes_; - scoped_ptr<base::Environment> env(base::Environment::Create()); + scoped_ptr<Environment> env(Environment::Create()); base::nix::DesktopEnvironment desktop_env = base::nix::GetDesktopEnvironment(env.get()); if (desktop_env == base::nix::DESKTOP_ENVIRONMENT_KDE3 || @@ -577,14 +575,14 @@ MimeUtilConstants::~MimeUtilConstants() { std::string GetFileMimeType(const FilePath& filepath) { if (filepath.empty()) return std::string(); - base::ThreadRestrictions::AssertIOAllowed(); - base::AutoLock scoped_lock(g_mime_util_xdg_lock.Get()); + ThreadRestrictions::AssertIOAllowed(); + AutoLock scoped_lock(g_mime_util_xdg_lock.Get()); return xdg_mime_get_mime_type_from_file_name(filepath.value().c_str()); } std::string GetDataMimeType(const std::string& data) { - base::ThreadRestrictions::AssertIOAllowed(); - base::AutoLock scoped_lock(g_mime_util_xdg_lock.Get()); + ThreadRestrictions::AssertIOAllowed(); + AutoLock scoped_lock(g_mime_util_xdg_lock.Get()); return xdg_mime_get_mime_type_for_data(data.data(), data.length(), NULL); } @@ -599,13 +597,13 @@ void SetIconThemeName(const std::string& name) { } FilePath GetMimeIcon(const std::string& mime_type, size_t size) { - base::ThreadRestrictions::AssertIOAllowed(); + ThreadRestrictions::AssertIOAllowed(); std::vector<std::string> icon_names; std::string icon_name; FilePath icon_file; if (!mime_type.empty()) { - base::AutoLock scoped_lock(g_mime_util_xdg_lock.Get()); + AutoLock scoped_lock(g_mime_util_xdg_lock.Get()); const char *icon = xdg_mime_get_icon(mime_type.c_str()); icon_name = std::string(icon ? icon : ""); } diff --git a/chromium/base/nix/xdg_util.cc b/chromium/base/nix/xdg_util.cc index b3caf2abe0b..4c1510f5c11 100644 --- a/chromium/base/nix/xdg_util.cc +++ b/chromium/base/nix/xdg_util.cc @@ -31,7 +31,7 @@ FilePath GetXDGDirectory(Environment* env, const char* env_name, if (env->GetVar(env_name, &env_value) && !env_value.empty()) path = FilePath(env_value); else - path = file_util::GetHomeDir().Append(fallback_dir); + path = GetHomeDir().Append(fallback_dir); return path.StripTrailingSeparators(); } @@ -42,7 +42,7 @@ FilePath GetXDGUserDirectory(const char* dir_name, const char* fallback_dir) { path = FilePath(xdg_dir); free(xdg_dir); } else { - path = file_util::GetHomeDir().Append(fallback_dir); + path = GetHomeDir().Append(fallback_dir); } return path.StripTrailingSeparators(); } diff --git a/chromium/base/observer_list.h b/chromium/base/observer_list.h index b44e33f56c7..bd1dc64055b 100644 --- a/chromium/base/observer_list.h +++ b/chromium/base/observer_list.h @@ -203,14 +203,15 @@ class ObserverList : public ObserverListBase<ObserverType> { } }; -#define FOR_EACH_OBSERVER(ObserverType, observer_list, func) \ - do { \ - if ((observer_list).might_have_observers()) { \ - ObserverListBase<ObserverType>::Iterator it(observer_list); \ - ObserverType* obs; \ - while ((obs = it.GetNext()) != NULL) \ - obs->func; \ - } \ +#define FOR_EACH_OBSERVER(ObserverType, observer_list, func) \ + do { \ + if ((observer_list).might_have_observers()) { \ + ObserverListBase<ObserverType>::Iterator \ + it_inside_observer_macro(observer_list); \ + ObserverType* obs; \ + while ((obs = it_inside_observer_macro.GetNext()) != NULL) \ + obs->func; \ + } \ } while (0) #endif // BASE_OBSERVER_LIST_H__ diff --git a/chromium/base/os_compat_android_unittest.cc b/chromium/base/os_compat_android_unittest.cc index c749b6a223d..a1d1fb19c91 100644 --- a/chromium/base/os_compat_android_unittest.cc +++ b/chromium/base/os_compat_android_unittest.cc @@ -17,7 +17,7 @@ typedef testing::Test OsCompatAndroidTest; // passes. TEST_F(OsCompatAndroidTest, DISABLED_TestMkdTemp) { FilePath tmp_dir; - EXPECT_TRUE(file_util::GetTempDir(&tmp_dir)); + EXPECT_TRUE(base::GetTempDir(&tmp_dir)); // Not six XXXXXX at the suffix of the path. FilePath sub_dir = tmp_dir.Append("XX"); diff --git a/chromium/base/path_service.cc b/chromium/base/path_service.cc index 89e58b2cdb1..f0a6a844158 100644 --- a/chromium/base/path_service.cc +++ b/chromium/base/path_service.cc @@ -254,7 +254,7 @@ bool PathService::OverrideAndCreateIfNeeded(int key, // this to the absolute path because on POSIX, MakeAbsoluteFilePath fails // if called on a non-existent path. if (!base::PathExists(file_path) && - !file_util::CreateDirectory(file_path)) + !base::CreateDirectory(file_path)) return false; } diff --git a/chromium/base/pickle.cc b/chromium/base/pickle.cc index af3191b9d86..12a32378788 100644 --- a/chromium/base/pickle.cc +++ b/chromium/base/pickle.cc @@ -10,6 +10,9 @@ //------------------------------------------------------------------------------ +using base::char16; +using base::string16; + // static const int Pickle::kPayloadUnit = 64; @@ -91,7 +94,15 @@ bool PickleIterator::ReadUInt64(uint64* result) { } bool PickleIterator::ReadFloat(float* result) { - return ReadBuiltinType(result); + // crbug.com/315213 + // The source data may not be properly aligned, and unaligned float reads + // cause SIGBUS on some ARM platforms, so force using memcpy to copy the data + // into the result. + const char* read_from = GetReadPointerAndAdvance<float>(); + if (!read_from) + return false; + memcpy(result, read_from, sizeof(*result)); + return true; } bool PickleIterator::ReadString(std::string* result) { @@ -153,8 +164,8 @@ bool PickleIterator::ReadBytes(const char** data, int length) { Pickle::Pickle() : header_(NULL), header_size_(sizeof(Header)), - capacity_(0), - variable_buffer_offset_(0) { + capacity_after_header_(0), + write_offset_(0) { Resize(kPayloadUnit); header_->payload_size = 0; } @@ -162,8 +173,8 @@ Pickle::Pickle() Pickle::Pickle(int header_size) : header_(NULL), header_size_(AlignInt(header_size, sizeof(uint32))), - capacity_(0), - variable_buffer_offset_(0) { + capacity_after_header_(0), + write_offset_(0) { DCHECK_GE(static_cast<size_t>(header_size), sizeof(Header)); DCHECK_LE(header_size, kPayloadUnit); Resize(kPayloadUnit); @@ -173,8 +184,8 @@ Pickle::Pickle(int header_size) Pickle::Pickle(const char* data, int data_len) : header_(reinterpret_cast<Header*>(const_cast<char*>(data))), header_size_(0), - capacity_(kCapacityReadOnly), - variable_buffer_offset_(0) { + capacity_after_header_(kCapacityReadOnly), + write_offset_(0) { if (data_len >= static_cast<int>(sizeof(Header))) header_size_ = data_len - header_->payload_size; @@ -192,16 +203,15 @@ Pickle::Pickle(const char* data, int data_len) Pickle::Pickle(const Pickle& other) : header_(NULL), header_size_(other.header_size_), - capacity_(0), - variable_buffer_offset_(other.variable_buffer_offset_) { + capacity_after_header_(0), + write_offset_(other.write_offset_) { size_t payload_size = header_size_ + other.header_->payload_size; - bool resized = Resize(payload_size); - CHECK(resized); // Realloc failed. + Resize(payload_size); memcpy(header_, other.header_, payload_size); } Pickle::~Pickle() { - if (capacity_ != kCapacityReadOnly) + if (capacity_after_header_ != kCapacityReadOnly) free(header_); } @@ -210,20 +220,19 @@ Pickle& Pickle::operator=(const Pickle& other) { NOTREACHED(); return *this; } - if (capacity_ == kCapacityReadOnly) { + if (capacity_after_header_ == kCapacityReadOnly) { header_ = NULL; - capacity_ = 0; + capacity_after_header_ = 0; } if (header_size_ != other.header_size_) { free(header_); header_ = NULL; header_size_ = other.header_size_; } - bool resized = Resize(other.header_size_ + other.header_->payload_size); - CHECK(resized); // Realloc failed. + Resize(other.header_->payload_size); memcpy(header_, other.header_, other.header_size_ + other.header_->payload_size); - variable_buffer_offset_ = other.variable_buffer_offset_; + write_offset_ = other.write_offset_; return *this; } @@ -254,91 +263,31 @@ bool Pickle::WriteData(const char* data, int length) { return length >= 0 && WriteInt(length) && WriteBytes(data, length); } -bool Pickle::WriteBytes(const void* data, int data_len) { - DCHECK_NE(kCapacityReadOnly, capacity_) << "oops: pickle is readonly"; - - char* dest = BeginWrite(data_len); - if (!dest) - return false; - - memcpy(dest, data, data_len); - - EndWrite(dest, data_len); +bool Pickle::WriteBytes(const void* data, int length) { + WriteBytesCommon(data, length); return true; } -char* Pickle::BeginWriteData(int length) { - DCHECK_EQ(variable_buffer_offset_, 0U) << - "There can only be one variable buffer in a Pickle"; - - if (length < 0 || !WriteInt(length)) - return NULL; - - char *data_ptr = BeginWrite(length); - if (!data_ptr) - return NULL; - - variable_buffer_offset_ = - data_ptr - reinterpret_cast<char*>(header_) - sizeof(int); - - // EndWrite doesn't necessarily have to be called after the write operation, - // so we call it here to pad out what the caller will eventually write. - EndWrite(data_ptr, length); - return data_ptr; -} - -void Pickle::TrimWriteData(int new_length) { - DCHECK_NE(variable_buffer_offset_, 0U); - - // Fetch the the variable buffer size - int* cur_length = reinterpret_cast<int*>( - reinterpret_cast<char*>(header_) + variable_buffer_offset_); - - if (new_length < 0 || new_length > *cur_length) { - NOTREACHED() << "Invalid length in TrimWriteData."; - return; - } - - // Update the payload size and variable buffer size - header_->payload_size -= (*cur_length - new_length); - *cur_length = new_length; -} - -char* Pickle::BeginWrite(size_t length) { - // write at a uint32-aligned offset from the beginning of the header - size_t offset = AlignInt(header_->payload_size, sizeof(uint32)); - - size_t new_size = offset + length; - size_t needed_size = header_size_ + new_size; - if (needed_size > capacity_ && !Resize(std::max(capacity_ * 2, needed_size))) - return NULL; - +void Pickle::Reserve(size_t length) { + size_t data_len = AlignInt(length, sizeof(uint32)); + DCHECK_GE(data_len, length); #ifdef ARCH_CPU_64_BITS - DCHECK_LE(length, kuint32max); + DCHECK_LE(data_len, kuint32max); #endif - - header_->payload_size = static_cast<uint32>(new_size); - return mutable_payload() + offset; + DCHECK_LE(write_offset_, kuint32max - data_len); + size_t new_size = write_offset_ + data_len; + if (new_size > capacity_after_header_) + Resize(capacity_after_header_ * 2 + new_size); } -void Pickle::EndWrite(char* dest, int length) { - // Zero-pad to keep tools like valgrind from complaining about uninitialized - // memory. - if (length % sizeof(uint32)) - memset(dest + length, 0, sizeof(uint32) - (length % sizeof(uint32))); -} - -bool Pickle::Resize(size_t new_capacity) { +void Pickle::Resize(size_t new_capacity) { new_capacity = AlignInt(new_capacity, kPayloadUnit); - CHECK_NE(capacity_, kCapacityReadOnly); - void* p = realloc(header_, new_capacity); - if (!p) - return false; - + CHECK_NE(capacity_after_header_, kCapacityReadOnly); + void* p = realloc(header_, header_size_ + new_capacity); + CHECK(p); header_ = reinterpret_cast<Header*>(p); - capacity_ = new_capacity; - return true; + capacity_after_header_ = new_capacity; } // static @@ -348,14 +297,41 @@ const char* Pickle::FindNext(size_t header_size, DCHECK_EQ(header_size, AlignInt(header_size, sizeof(uint32))); DCHECK_LE(header_size, static_cast<size_t>(kPayloadUnit)); - if (static_cast<size_t>(end - start) < sizeof(Header)) + size_t length = static_cast<size_t>(end - start); + if (length < sizeof(Header)) return NULL; const Header* hdr = reinterpret_cast<const Header*>(start); - const char* payload_base = start + header_size; - const char* payload_end = payload_base + hdr->payload_size; - if (payload_end < payload_base) + if (length < header_size || length - header_size < hdr->payload_size) return NULL; + return start + header_size + hdr->payload_size; +} + +template <size_t length> void Pickle::WriteBytesStatic(const void* data) { + WriteBytesCommon(data, length); +} + +template void Pickle::WriteBytesStatic<2>(const void* data); +template void Pickle::WriteBytesStatic<4>(const void* data); +template void Pickle::WriteBytesStatic<8>(const void* data); + +inline void Pickle::WriteBytesCommon(const void* data, size_t length) { + DCHECK_NE(kCapacityReadOnly, capacity_after_header_) + << "oops: pickle is readonly"; + size_t data_len = AlignInt(length, sizeof(uint32)); + DCHECK_GE(data_len, length); +#ifdef ARCH_CPU_64_BITS + DCHECK_LE(data_len, kuint32max); +#endif + DCHECK_LE(write_offset_, kuint32max - data_len); + size_t new_size = write_offset_ + data_len; + if (new_size > capacity_after_header_) { + Resize(std::max(capacity_after_header_ * 2, new_size)); + } - return (payload_end > end) ? NULL : payload_end; + char* write = mutable_payload() + write_offset_; + memcpy(write, data, length); + memset(write + length, 0, data_len - length); + header_->payload_size = static_cast<uint32>(write_offset_ + length); + write_offset_ = new_size; } diff --git a/chromium/base/pickle.h b/chromium/base/pickle.h index 3de2886e5d4..dd34f54176c 100644 --- a/chromium/base/pickle.h +++ b/chromium/base/pickle.h @@ -37,7 +37,7 @@ class BASE_EXPORT PickleIterator { bool ReadFloat(float* result) WARN_UNUSED_RESULT; bool ReadString(std::string* result) WARN_UNUSED_RESULT; bool ReadWString(std::wstring* result) WARN_UNUSED_RESULT; - bool ReadString16(string16* result) WARN_UNUSED_RESULT; + bool ReadString16(base::string16* result) WARN_UNUSED_RESULT; bool ReadData(const char** data, int* length) WARN_UNUSED_RESULT; bool ReadBytes(const char** data, int length) WARN_UNUSED_RESULT; @@ -138,57 +138,73 @@ class BASE_EXPORT Pickle { // For compatibility, these older style read methods pass through to the // PickleIterator methods. // TODO(jbates) Remove these methods. - bool ReadBool(PickleIterator* iter, bool* result) const { + bool ReadBool(PickleIterator* iter, + bool* result) const WARN_UNUSED_RESULT { return iter->ReadBool(result); } - bool ReadInt(PickleIterator* iter, int* result) const { + bool ReadInt(PickleIterator* iter, + int* result) const WARN_UNUSED_RESULT { return iter->ReadInt(result); } - bool ReadLong(PickleIterator* iter, long* result) const { + bool ReadLong(PickleIterator* iter, + long* result) const WARN_UNUSED_RESULT { return iter->ReadLong(result); } - bool ReadUInt16(PickleIterator* iter, uint16* result) const { + bool ReadUInt16(PickleIterator* iter, + uint16* result) const WARN_UNUSED_RESULT { return iter->ReadUInt16(result); } - bool ReadUInt32(PickleIterator* iter, uint32* result) const { + bool ReadUInt32(PickleIterator* iter, + uint32* result) const WARN_UNUSED_RESULT { return iter->ReadUInt32(result); } - bool ReadInt64(PickleIterator* iter, int64* result) const { + bool ReadInt64(PickleIterator* iter, + int64* result) const WARN_UNUSED_RESULT { return iter->ReadInt64(result); } - bool ReadUInt64(PickleIterator* iter, uint64* result) const { + bool ReadUInt64(PickleIterator* iter, + uint64* result) const WARN_UNUSED_RESULT { return iter->ReadUInt64(result); } - bool ReadFloat(PickleIterator* iter, float* result) const { + bool ReadFloat(PickleIterator* iter, + float* result) const WARN_UNUSED_RESULT { return iter->ReadFloat(result); } - bool ReadString(PickleIterator* iter, std::string* result) const { + bool ReadString(PickleIterator* iter, + std::string* result) const WARN_UNUSED_RESULT { return iter->ReadString(result); } - bool ReadWString(PickleIterator* iter, std::wstring* result) const { + bool ReadWString(PickleIterator* iter, + std::wstring* result) const WARN_UNUSED_RESULT { return iter->ReadWString(result); } - bool ReadString16(PickleIterator* iter, string16* result) const { + bool ReadString16(PickleIterator* iter, + base::string16* result) const WARN_UNUSED_RESULT { return iter->ReadString16(result); } // A pointer to the data will be placed in *data, and the length will be // placed in *length. This buffer will be into the message's buffer so will // be scoped to the lifetime of the message (or until the message data is // mutated). - bool ReadData(PickleIterator* iter, const char** data, int* length) const { + bool ReadData(PickleIterator* iter, + const char** data, + int* length) const WARN_UNUSED_RESULT { return iter->ReadData(data, length); } // A pointer to the data will be placed in *data. The caller specifies the // number of bytes to read, and ReadBytes will validate this length. The // returned buffer will be into the message's buffer so will be scoped to the // lifetime of the message (or until the message data is mutated). - bool ReadBytes(PickleIterator* iter, const char** data, int length) const { + bool ReadBytes(PickleIterator* iter, + const char** data, + int length) const WARN_UNUSED_RESULT { return iter->ReadBytes(data, length); } // Safer version of ReadInt() checks for the result not being negative. // Use it for reading the object sizes. - bool ReadLength(PickleIterator* iter, int* result) const { + bool ReadLength(PickleIterator* iter, + int* result) const WARN_UNUSED_RESULT { return iter->ReadLength(result); } @@ -200,7 +216,7 @@ class BASE_EXPORT Pickle { return WriteInt(value ? 1 : 0); } bool WriteInt(int value) { - return WriteBytes(&value, sizeof(value)); + return WritePOD(value); } // WARNING: DO NOT USE THIS METHOD IF PICKLES ARE PERSISTED IN ANY WAY. // It will write whatever a "long" is on this architecture. On 32-bit @@ -208,54 +224,38 @@ class BASE_EXPORT Pickle { // pickles are still around after upgrading to 64-bit, or if they are copied // between dissimilar systems, YOUR PICKLES WILL HAVE GONE BAD. bool WriteLongUsingDangerousNonPortableLessPersistableForm(long value) { - return WriteBytes(&value, sizeof(value)); + return WritePOD(value); } bool WriteUInt16(uint16 value) { - return WriteBytes(&value, sizeof(value)); + return WritePOD(value); } bool WriteUInt32(uint32 value) { - return WriteBytes(&value, sizeof(value)); + return WritePOD(value); } bool WriteInt64(int64 value) { - return WriteBytes(&value, sizeof(value)); + return WritePOD(value); } bool WriteUInt64(uint64 value) { - return WriteBytes(&value, sizeof(value)); + return WritePOD(value); } bool WriteFloat(float value) { - return WriteBytes(&value, sizeof(value)); + return WritePOD(value); } bool WriteString(const std::string& value); bool WriteWString(const std::wstring& value); - bool WriteString16(const string16& value); + bool WriteString16(const base::string16& value); // "Data" is a blob with a length. When you read it out you will be given the // length. See also WriteBytes. bool WriteData(const char* data, int length); - // "Bytes" is a blob with no length. The caller must specify the lenght both + // "Bytes" is a blob with no length. The caller must specify the length both // when reading and writing. It is normally used to serialize PoD types of a // known size. See also WriteData. - bool WriteBytes(const void* data, int data_len); - - // Same as WriteData, but allows the caller to write directly into the - // Pickle. This saves a copy in cases where the data is not already - // available in a buffer. The caller should take care to not write more - // than the length it declares it will. Use ReadData to get the data. - // Returns NULL on failure. - // - // The returned pointer will only be valid until the next write operation - // on this Pickle. - char* BeginWriteData(int length); - - // For Pickles which contain variable length buffers (e.g. those created - // with BeginWriteData), the Pickle can - // be 'trimmed' if the amount of data required is less than originally - // requested. For example, you may have created a buffer with 10K of data, - // but decided to only fill 10 bytes of that data. Use this function - // to trim the buffer so that we don't send 9990 bytes of unused data. - // You cannot increase the size of the variable buffer; only shrink it. - // This function assumes that the length of the variable buffer has - // not been changed. - void TrimWriteData(int length); + bool WriteBytes(const void* data, int length); + + // Reserves space for upcoming writes when multiple writes will be made and + // their sizes are computed in advance. It can be significantly faster to call + // Reserve() before calling WriteFoo() multiple times. + void Reserve(size_t additional_capacity); // Payload follows after allocation of Header (header size is customizable). struct Header { @@ -295,26 +295,13 @@ class BASE_EXPORT Pickle { return reinterpret_cast<char*>(header_) + header_size_; } - size_t capacity() const { - return capacity_; + size_t capacity_after_header() const { + return capacity_after_header_; } - // Resizes the buffer for use when writing the specified amount of data. The - // location that the data should be written at is returned, or NULL if there - // was an error. Call EndWrite with the returned offset and the given length - // to pad out for the next write. - char* BeginWrite(size_t length); - - // Completes the write operation by padding the data with NULL bytes until it - // is padded. Should be paired with BeginWrite, but it does not necessarily - // have to be called after the data is written. - void EndWrite(char* dest, int length); - - // Resize the capacity, note that the input value should include the size of - // the header: new_capacity = sizeof(Header) + desired_payload_capacity. - // A realloc() failure will cause a Resize failure... and caller should check - // the return result for true (i.e., successful resizing). - bool Resize(size_t new_capacity); + // Resize the capacity, note that the input value should not include the size + // of the header. + void Resize(size_t new_capacity); // Aligns 'i' by rounding it up to the next multiple of 'alignment' static size_t AlignInt(size_t i, int alignment) { @@ -335,13 +322,27 @@ class BASE_EXPORT Pickle { Header* header_; size_t header_size_; // Supports extra data between header and payload. - // Allocation size of payload (or -1 if allocation is const). - size_t capacity_; - size_t variable_buffer_offset_; // IF non-zero, then offset to a buffer. + // Allocation size of payload (or -1 if allocation is const). Note: this + // doesn't count the header. + size_t capacity_after_header_; + // The offset at which we will write the next field. Note: this doesn't count + // the header. + size_t write_offset_; + + // Just like WriteBytes, but with a compile-time size, for performance. + template<size_t length> void WriteBytesStatic(const void* data); + + // Writes a POD by copying its bytes. + template <typename T> bool WritePOD(const T& data) { + WriteBytesStatic<sizeof(data)>(&data); + return true; + } + inline void WriteBytesCommon(const void* data, size_t length); FRIEND_TEST_ALL_PREFIXES(PickleTest, Resize); FRIEND_TEST_ALL_PREFIXES(PickleTest, FindNext); FRIEND_TEST_ALL_PREFIXES(PickleTest, FindNextWithIncompleteHeader); + FRIEND_TEST_ALL_PREFIXES(PickleTest, FindNextOverflow); }; #endif // BASE_PICKLE_H__ diff --git a/chromium/base/pickle_unittest.cc b/chromium/base/pickle_unittest.cc index cc384c70617..b1c5925f668 100644 --- a/chromium/base/pickle_unittest.cc +++ b/chromium/base/pickle_unittest.cc @@ -10,6 +10,9 @@ #include "base/strings/string16.h" #include "testing/gtest/include/gtest/gtest.h" +// Remove when this file is in the base namespace. +using base::string16; + namespace { const int testint = 2093847192; @@ -58,10 +61,6 @@ void VerifyResult(const Pickle& pickle) { EXPECT_EQ(testdatalen, outdatalen); EXPECT_EQ(memcmp(testdata, outdata, outdatalen), 0); - EXPECT_TRUE(pickle.ReadData(&iter, &outdata, &outdatalen)); - EXPECT_EQ(testdatalen, outdatalen); - EXPECT_EQ(memcmp(testdata, outdata, outdatalen), 0); - // reads past the end should fail EXPECT_FALSE(pickle.ReadInt(&iter, &outint)); } @@ -79,14 +78,6 @@ TEST(PickleTest, EncodeDecode) { EXPECT_TRUE(pickle.WriteUInt16(testuint16)); EXPECT_TRUE(pickle.WriteFloat(testfloat)); EXPECT_TRUE(pickle.WriteData(testdata, testdatalen)); - - // Over allocate BeginWriteData so we can test TrimWriteData. - char* dest = pickle.BeginWriteData(testdatalen + 100); - EXPECT_TRUE(dest); - memcpy(dest, testdata, testdatalen); - - pickle.TrimWriteData(testdatalen); - VerifyResult(pickle); // test copy constructor @@ -194,6 +185,37 @@ TEST(PickleTest, FindNextWithIncompleteHeader) { EXPECT_TRUE(NULL == Pickle::FindNext(header_size, start, end)); } +#if defined(COMPILER_MSVC) +#pragma warning(push) +#pragma warning(disable: 4146) +#endif +TEST(PickleTest, FindNextOverflow) { + size_t header_size = sizeof(Pickle::Header); + size_t header_size2 = 2 * header_size; + size_t payload_received = 100; + scoped_ptr<char[]> buffer(new char[header_size2 + payload_received]); + const char* start = buffer.get(); + Pickle::Header* header = reinterpret_cast<Pickle::Header*>(buffer.get()); + const char* end = start + header_size2 + payload_received; + // It is impossible to construct an overflow test otherwise. + if (sizeof(size_t) > sizeof(header->payload_size) || + sizeof(uintptr_t) > sizeof(header->payload_size)) + return; + + header->payload_size = -(reinterpret_cast<uintptr_t>(start) + header_size2); + EXPECT_TRUE(NULL == Pickle::FindNext(header_size2, start, end)); + + header->payload_size = -header_size2; + EXPECT_TRUE(NULL == Pickle::FindNext(header_size2, start, end)); + + header->payload_size = 0; + end = start + header_size; + EXPECT_TRUE(NULL == Pickle::FindNext(header_size2, start, end)); +} +#if defined(COMPILER_MSVC) +#pragma warning(pop) +#endif + TEST(PickleTest, GetReadPointerAndAdvance) { Pickle pickle; @@ -229,19 +251,19 @@ TEST(PickleTest, Resize) { size_t cur_payload = payload_size_after_header; // note: we assume 'unit' is a power of 2 - EXPECT_EQ(unit, pickle.capacity()); + EXPECT_EQ(unit, pickle.capacity_after_header()); EXPECT_EQ(pickle.payload_size(), payload_size_after_header); // fill out a full page (noting data header) pickle.WriteData(data_ptr, static_cast<int>(unit - sizeof(uint32))); cur_payload += unit; - EXPECT_EQ(unit * 2, pickle.capacity()); + EXPECT_EQ(unit * 2, pickle.capacity_after_header()); EXPECT_EQ(cur_payload, pickle.payload_size()); // one more byte should double the capacity pickle.WriteData(data_ptr, 1); cur_payload += 5; - EXPECT_EQ(unit * 4, pickle.capacity()); + EXPECT_EQ(unit * 4, pickle.capacity_after_header()); EXPECT_EQ(cur_payload, pickle.payload_size()); } diff --git a/chromium/base/platform_file.h b/chromium/base/platform_file.h index e94c7ed0e3f..d9a82cbd3f4 100644 --- a/chromium/base/platform_file.h +++ b/chromium/base/platform_file.h @@ -19,11 +19,18 @@ namespace base { +// *************************************************************************** +// ***** Don't use anything from this file anymore. It is being removed! +// ***** Use base/files/base_file.h instead +// *************************************************************************** + // PLATFORM_FILE_(OPEN|CREATE).* are mutually exclusive. You should specify // exactly one of the five (possibly combining with other flags) when opening // or creating a file. // PLATFORM_FILE_(WRITE|APPEND) are mutually exclusive. This is so that APPEND // behavior will be consistent with O_APPEND on POSIX. +// PLATFORM_FILE_EXCLUSIVE_(READ|WRITE) only grant exclusive access to the file +// on creation on POSIX; for existing files, consider using LockPlatformFile(). enum PlatformFileFlags { PLATFORM_FILE_OPEN = 1 << 0, // Opens a file, only if it exists. PLATFORM_FILE_CREATE = 1 << 1, // Creates a new file, only if it @@ -44,16 +51,19 @@ enum PlatformFileFlags { PLATFORM_FILE_DELETE_ON_CLOSE = 1 << 13, PLATFORM_FILE_WRITE_ATTRIBUTES = 1 << 14, // Used on Windows only - PLATFORM_FILE_ENUMERATE = 1 << 15, // May enumerate directory - PLATFORM_FILE_SHARE_DELETE = 1 << 16, // Used on Windows only + PLATFORM_FILE_SHARE_DELETE = 1 << 15, // Used on Windows only - PLATFORM_FILE_TERMINAL_DEVICE = 1 << 17, // Serial port flags - PLATFORM_FILE_BACKUP_SEMANTICS = 1 << 18, // Used on Windows only + PLATFORM_FILE_TERMINAL_DEVICE = 1 << 16, // Serial port flags + PLATFORM_FILE_BACKUP_SEMANTICS = 1 << 17, // Used on Windows only - PLATFORM_FILE_EXECUTE = 1 << 19, // Used on Windows only + PLATFORM_FILE_EXECUTE = 1 << 18, // Used on Windows only }; +// This enum has been recorded in multiple histograms. If the order of the +// fields needs to change, please ensure that those histograms are obsolete or +// have been moved to a different enum. +// // PLATFORM_FILE_ERROR_ACCESS_DENIED is returned when a call fails because of // a filesystem restriction. PLATFORM_FILE_ERROR_SECURITY is returned when a // browser policy doesn't allow the operation to be executed. @@ -117,11 +127,11 @@ struct BASE_EXPORT PlatformFileInfo { #if defined(OS_WIN) typedef HANDLE PlatformFile; const PlatformFile kInvalidPlatformFileValue = INVALID_HANDLE_VALUE; -PlatformFileError LastErrorToPlatformFileError(DWORD saved_errno); +BASE_EXPORT PlatformFileError LastErrorToPlatformFileError(DWORD last_error); #elif defined(OS_POSIX) typedef int PlatformFile; const PlatformFile kInvalidPlatformFileValue = -1; -PlatformFileError ErrnoToPlatformFileError(int saved_errno); +BASE_EXPORT PlatformFileError ErrnoToPlatformFileError(int saved_errno); #endif // Creates or opens the given file. If |created| is provided, it will be set to @@ -211,6 +221,31 @@ BASE_EXPORT bool TouchPlatformFile(PlatformFile file, // Returns some information for the given file. BASE_EXPORT bool GetPlatformFileInfo(PlatformFile file, PlatformFileInfo* info); +// Attempts to take an exclusive write lock on the file. Returns immediately +// (i.e. does not wait for another process to unlock the file). If the lock +// was obtained, the result will be PLATFORM_FILE_OK. A lock only guarantees +// that other processes may not also take a lock on the same file with the +// same API - it may still be opened, renamed, unlinked, etc. +// +// Common semantics: +// * Locks are held by processes, but not inherited by child processes. +// * Locks are released by the OS on file handle close or process termination. +// * Locks are reliable only on local filesystems. +// * Duplicated file handles may also write to locked files. +// Windows-specific semantics: +// * Locks are mandatory for read/write APIs, advisory for mapping APIs. +// * Within a process, locking the same file (by the same or new handle) +// will fail. +// POSIX-specific semantics: +// * Locks are advisory only. +// * Within a process, locking the same file (by the same or new handle) +// will succeed. +// * Closing any descriptor on a given file releases the lock. +BASE_EXPORT PlatformFileError LockPlatformFile(PlatformFile file); + +// Unlock a file previously locked with LockPlatformFile. +BASE_EXPORT PlatformFileError UnlockPlatformFile(PlatformFile file); + // Use this class to pass ownership of a PlatformFile to a receiver that may or // may not want to accept it. This class does not own the storage for the // PlatformFile. diff --git a/chromium/base/platform_file_posix.cc b/chromium/base/platform_file_posix.cc index 056577c5817..028a38276d6 100644 --- a/chromium/base/platform_file_posix.cc +++ b/chromium/base/platform_file_posix.cc @@ -46,15 +46,6 @@ static int CallFstat(int fd, stat_wrapper_t *sb) { // NaCl doesn't provide the following system calls, so either simulate them or // wrap them in order to minimize the number of #ifdef's in this file. #if !defined(OS_NACL) -static int DoPread(PlatformFile file, char* data, int size, int64 offset) { - return HANDLE_EINTR(pread(file, data, size, offset)); -} - -static int DoPwrite(PlatformFile file, const char* data, int size, - int64 offset) { - return HANDLE_EINTR(pwrite(file, data, size, offset)); -} - static bool IsOpenAppend(PlatformFile file) { return (fcntl(file, F_GETFL) & O_APPEND) != 0; } @@ -83,18 +74,18 @@ static int CallFutimes(PlatformFile file, const struct timeval times[2]) { return futimes(file, times); #endif } -#else // defined(OS_NACL) -// TODO(bbudge) Remove DoPread, DoPwrite when NaCl implements pread, pwrite. -static int DoPread(PlatformFile file, char* data, int size, int64 offset) { - lseek(file, static_cast<off_t>(offset), SEEK_SET); - return HANDLE_EINTR(read(file, data, size)); -} -static int DoPwrite(PlatformFile file, const char* data, int size, - int64 offset) { - lseek(file, static_cast<off_t>(offset), SEEK_SET); - return HANDLE_EINTR(write(file, data, size)); +static PlatformFileError CallFctnlFlock(PlatformFile file, bool do_lock) { + struct flock lock; + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_SET; + lock.l_start = 0; + lock.l_len = 0; // Lock entire file. + if (HANDLE_EINTR(fcntl(file, do_lock ? F_SETLK : F_UNLCK, &lock)) == -1) + return ErrnoToPlatformFileError(errno); + return PLATFORM_FILE_OK; } +#else // defined(OS_NACL) static bool IsOpenAppend(PlatformFile file) { // NaCl doesn't implement fcntl. Since NaCl's write conforms to the POSIX @@ -117,6 +108,11 @@ static int CallFutimes(PlatformFile file, const struct timeval times[2]) { NOTIMPLEMENTED(); // NaCl doesn't implement futimes. return 0; } + +static PlatformFileError CallFctnlFlock(PlatformFile file, bool do_lock) { + NOTIMPLEMENTED(); // NaCl doesn't implement flock struct. + return PLATFORM_FILE_ERROR_INVALID_OPERATION; +} #endif // defined(OS_NACL) } // namespace @@ -225,7 +221,7 @@ FILE* FdopenPlatformFile(PlatformFile file, const char* mode) { bool ClosePlatformFile(PlatformFile file) { base::ThreadRestrictions::AssertIOAllowed(); - return !HANDLE_EINTR(close(file)); + return !IGNORE_EINTR(close(file)); } int64 SeekPlatformFile(PlatformFile file, @@ -246,8 +242,8 @@ int ReadPlatformFile(PlatformFile file, int64 offset, char* data, int size) { int bytes_read = 0; int rv; do { - rv = DoPread(file, data + bytes_read, - size - bytes_read, offset + bytes_read); + rv = HANDLE_EINTR(pread(file, data + bytes_read, + size - bytes_read, offset + bytes_read)); if (rv <= 0) break; @@ -281,7 +277,7 @@ int ReadPlatformFileNoBestEffort(PlatformFile file, int64 offset, if (file < 0) return -1; - return DoPread(file, data, size, offset); + return HANDLE_EINTR(pread(file, data, size, offset)); } int ReadPlatformFileCurPosNoBestEffort(PlatformFile file, @@ -306,8 +302,8 @@ int WritePlatformFile(PlatformFile file, int64 offset, int bytes_written = 0; int rv; do { - rv = DoPwrite(file, data + bytes_written, - size - bytes_written, offset + bytes_written); + rv = HANDLE_EINTR(pwrite(file, data + bytes_written, + size - bytes_written, offset + bytes_written)); if (rv <= 0) break; @@ -426,6 +422,14 @@ bool GetPlatformFileInfo(PlatformFile file, PlatformFileInfo* info) { return true; } +PlatformFileError LockPlatformFile(PlatformFile file) { + return CallFctnlFlock(file, true); +} + +PlatformFileError UnlockPlatformFile(PlatformFile file) { + return CallFctnlFlock(file, false); +} + PlatformFileError ErrnoToPlatformFileError(int saved_errno) { switch (saved_errno) { case EACCES: diff --git a/chromium/base/platform_file_unittest.cc b/chromium/base/platform_file_unittest.cc index a1f3927eddd..9cf66a9369a 100644 --- a/chromium/base/platform_file_unittest.cc +++ b/chromium/base/platform_file_unittest.cc @@ -8,151 +8,151 @@ #include "base/time/time.h" #include "testing/gtest/include/gtest/gtest.h" -using base::FilePath; +namespace base { namespace { // Reads from a file the given number of bytes, or until EOF is reached. // Returns the number of bytes read. -int ReadFully(base::PlatformFile file, int64 offset, char* data, int size) { - return base::ReadPlatformFile(file, offset, data, size); +int ReadFully(PlatformFile file, int64 offset, char* data, int size) { + return ReadPlatformFile(file, offset, data, size); } // Writes the given number of bytes to a file. // Returns the number of bytes written. -int WriteFully(base::PlatformFile file, int64 offset, +int WriteFully(PlatformFile file, int64 offset, const char* data, int size) { - return base::WritePlatformFile(file, offset, data, size); + return WritePlatformFile(file, offset, data, size); } } // namespace TEST(PlatformFile, CreatePlatformFile) { - base::ScopedTempDir temp_dir; + ScopedTempDir temp_dir; ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); FilePath file_path = temp_dir.path().AppendASCII("create_file_1"); // Open a file that doesn't exist. - base::PlatformFileError error_code = base::PLATFORM_FILE_OK; - base::PlatformFile file = base::CreatePlatformFile( + PlatformFileError error_code = PLATFORM_FILE_OK; + PlatformFile file = CreatePlatformFile( file_path, - base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ, + PLATFORM_FILE_OPEN | PLATFORM_FILE_READ, NULL, &error_code); - EXPECT_EQ(base::kInvalidPlatformFileValue, file); - EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, error_code); + EXPECT_EQ(kInvalidPlatformFileValue, file); + EXPECT_EQ(PLATFORM_FILE_ERROR_NOT_FOUND, error_code); // Open or create a file. bool created = false; - error_code = base::PLATFORM_FILE_OK; - file = base::CreatePlatformFile( + error_code = PLATFORM_FILE_OK; + file = CreatePlatformFile( file_path, - base::PLATFORM_FILE_OPEN_ALWAYS | base::PLATFORM_FILE_READ, + PLATFORM_FILE_OPEN_ALWAYS | PLATFORM_FILE_READ, &created, &error_code); - EXPECT_NE(base::kInvalidPlatformFileValue, file); + EXPECT_NE(kInvalidPlatformFileValue, file); EXPECT_TRUE(created); - EXPECT_EQ(base::PLATFORM_FILE_OK, error_code); - base::ClosePlatformFile(file); + EXPECT_EQ(PLATFORM_FILE_OK, error_code); + ClosePlatformFile(file); // Open an existing file. created = false; - file = base::CreatePlatformFile( + file = CreatePlatformFile( file_path, - base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ, + PLATFORM_FILE_OPEN | PLATFORM_FILE_READ, &created, &error_code); - EXPECT_NE(base::kInvalidPlatformFileValue, file); + EXPECT_NE(kInvalidPlatformFileValue, file); EXPECT_FALSE(created); - EXPECT_EQ(base::PLATFORM_FILE_OK, error_code); - base::ClosePlatformFile(file); + EXPECT_EQ(PLATFORM_FILE_OK, error_code); + ClosePlatformFile(file); // Create a file that exists. - file = base::CreatePlatformFile( + file = CreatePlatformFile( file_path, - base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_READ, + PLATFORM_FILE_CREATE | PLATFORM_FILE_READ, &created, &error_code); - EXPECT_EQ(base::kInvalidPlatformFileValue, file); + EXPECT_EQ(kInvalidPlatformFileValue, file); EXPECT_FALSE(created); - EXPECT_EQ(base::PLATFORM_FILE_ERROR_EXISTS, error_code); + EXPECT_EQ(PLATFORM_FILE_ERROR_EXISTS, error_code); // Create or overwrite a file. - error_code = base::PLATFORM_FILE_OK; - file = base::CreatePlatformFile( + error_code = PLATFORM_FILE_OK; + file = CreatePlatformFile( file_path, - base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_READ, + PLATFORM_FILE_CREATE_ALWAYS | PLATFORM_FILE_READ, &created, &error_code); - EXPECT_NE(base::kInvalidPlatformFileValue, file); + EXPECT_NE(kInvalidPlatformFileValue, file); EXPECT_TRUE(created); - EXPECT_EQ(base::PLATFORM_FILE_OK, error_code); - base::ClosePlatformFile(file); + EXPECT_EQ(PLATFORM_FILE_OK, error_code); + ClosePlatformFile(file); // Create a delete-on-close file. created = false; file_path = temp_dir.path().AppendASCII("create_file_2"); - file = base::CreatePlatformFile( + file = CreatePlatformFile( file_path, - base::PLATFORM_FILE_OPEN_ALWAYS | base::PLATFORM_FILE_DELETE_ON_CLOSE | - base::PLATFORM_FILE_READ, + PLATFORM_FILE_OPEN_ALWAYS | PLATFORM_FILE_DELETE_ON_CLOSE | + PLATFORM_FILE_READ, &created, &error_code); - EXPECT_NE(base::kInvalidPlatformFileValue, file); + EXPECT_NE(kInvalidPlatformFileValue, file); EXPECT_TRUE(created); - EXPECT_EQ(base::PLATFORM_FILE_OK, error_code); + EXPECT_EQ(PLATFORM_FILE_OK, error_code); - EXPECT_TRUE(base::ClosePlatformFile(file)); - EXPECT_FALSE(base::PathExists(file_path)); + EXPECT_TRUE(ClosePlatformFile(file)); + EXPECT_FALSE(PathExists(file_path)); } TEST(PlatformFile, DeleteOpenFile) { - base::ScopedTempDir temp_dir; + ScopedTempDir temp_dir; ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); FilePath file_path = temp_dir.path().AppendASCII("create_file_1"); // Create a file. bool created = false; - base::PlatformFileError error_code = base::PLATFORM_FILE_OK; - base::PlatformFile file = base::CreatePlatformFile( + PlatformFileError error_code = PLATFORM_FILE_OK; + PlatformFile file = CreatePlatformFile( file_path, - base::PLATFORM_FILE_OPEN_ALWAYS | base::PLATFORM_FILE_READ | - base::PLATFORM_FILE_SHARE_DELETE, + PLATFORM_FILE_OPEN_ALWAYS | PLATFORM_FILE_READ | + PLATFORM_FILE_SHARE_DELETE, &created, &error_code); - EXPECT_NE(base::kInvalidPlatformFileValue, file); + EXPECT_NE(kInvalidPlatformFileValue, file); EXPECT_TRUE(created); - EXPECT_EQ(base::PLATFORM_FILE_OK, error_code); + EXPECT_EQ(PLATFORM_FILE_OK, error_code); // Open an existing file and mark it as delete on close. created = false; - base::PlatformFile same_file = base::CreatePlatformFile( + PlatformFile same_file = CreatePlatformFile( file_path, - base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_DELETE_ON_CLOSE | - base::PLATFORM_FILE_READ, + PLATFORM_FILE_OPEN | PLATFORM_FILE_DELETE_ON_CLOSE | + PLATFORM_FILE_READ, &created, &error_code); - EXPECT_NE(base::kInvalidPlatformFileValue, file); + EXPECT_NE(kInvalidPlatformFileValue, file); EXPECT_FALSE(created); - EXPECT_EQ(base::PLATFORM_FILE_OK, error_code); + EXPECT_EQ(PLATFORM_FILE_OK, error_code); // Close both handles and check that the file is gone. - base::ClosePlatformFile(file); - base::ClosePlatformFile(same_file); - EXPECT_FALSE(base::PathExists(file_path)); + ClosePlatformFile(file); + ClosePlatformFile(same_file); + EXPECT_FALSE(PathExists(file_path)); } TEST(PlatformFile, ReadWritePlatformFile) { - base::ScopedTempDir temp_dir; + ScopedTempDir temp_dir; ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); FilePath file_path = temp_dir.path().AppendASCII("read_write_file"); - base::PlatformFile file = base::CreatePlatformFile( + PlatformFile file = CreatePlatformFile( file_path, - base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_READ | - base::PLATFORM_FILE_WRITE, + PLATFORM_FILE_CREATE | PLATFORM_FILE_READ | + PLATFORM_FILE_WRITE, NULL, NULL); - EXPECT_NE(base::kInvalidPlatformFileValue, file); + EXPECT_NE(kInvalidPlatformFileValue, file); char data_to_write[] = "test"; const int kTestDataSize = 4; @@ -188,7 +188,7 @@ TEST(PlatformFile, ReadWritePlatformFile) { EXPECT_EQ(data_to_write[i], data_read_1[i]); // Read again, but using the trivial native wrapper. - bytes_read = base::ReadPlatformFileNoBestEffort(file, 0, data_read_1, + bytes_read = ReadPlatformFileNoBestEffort(file, 0, data_read_1, kTestDataSize); EXPECT_LE(bytes_read, kTestDataSize); for (int i = 0; i < bytes_read; i++) @@ -203,7 +203,7 @@ TEST(PlatformFile, ReadWritePlatformFile) { // Make sure the file was extended. int64 file_size = 0; - EXPECT_TRUE(file_util::GetFileSize(file_path, &file_size)); + EXPECT_TRUE(GetFileSize(file_path, &file_size)); EXPECT_EQ(kOffsetBeyondEndOfFile + kPartialWriteLength, file_size); // Make sure the file was zero-padded. @@ -218,19 +218,19 @@ TEST(PlatformFile, ReadWritePlatformFile) { EXPECT_EQ(data_to_write[i - kOffsetBeyondEndOfFile], data_read_2[i]); // Close the file handle to allow the temp directory to be deleted. - base::ClosePlatformFile(file); + ClosePlatformFile(file); } TEST(PlatformFile, AppendPlatformFile) { - base::ScopedTempDir temp_dir; + ScopedTempDir temp_dir; ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); FilePath file_path = temp_dir.path().AppendASCII("append_file"); - base::PlatformFile file = base::CreatePlatformFile( + PlatformFile file = CreatePlatformFile( file_path, - base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_APPEND, + PLATFORM_FILE_CREATE | PLATFORM_FILE_APPEND, NULL, NULL); - EXPECT_NE(base::kInvalidPlatformFileValue, file); + EXPECT_NE(kInvalidPlatformFileValue, file); char data_to_write[] = "test"; const int kTestDataSize = 4; @@ -243,14 +243,14 @@ TEST(PlatformFile, AppendPlatformFile) { bytes_written = WriteFully(file, 0, data_to_write, kTestDataSize); EXPECT_EQ(kTestDataSize, bytes_written); - base::ClosePlatformFile(file); - file = base::CreatePlatformFile( + ClosePlatformFile(file); + file = CreatePlatformFile( file_path, - base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ | - base::PLATFORM_FILE_APPEND, + PLATFORM_FILE_OPEN | PLATFORM_FILE_READ | + PLATFORM_FILE_APPEND, NULL, NULL); - EXPECT_NE(base::kInvalidPlatformFileValue, file); + EXPECT_NE(kInvalidPlatformFileValue, file); char append_data_to_write[] = "78"; const int kAppendDataSize = 2; @@ -270,21 +270,21 @@ TEST(PlatformFile, AppendPlatformFile) { EXPECT_EQ(append_data_to_write[i], data_read_1[kTestDataSize + i]); // Close the file handle to allow the temp directory to be deleted. - base::ClosePlatformFile(file); + ClosePlatformFile(file); } TEST(PlatformFile, TruncatePlatformFile) { - base::ScopedTempDir temp_dir; + ScopedTempDir temp_dir; ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); FilePath file_path = temp_dir.path().AppendASCII("truncate_file"); - base::PlatformFile file = base::CreatePlatformFile( + PlatformFile file = CreatePlatformFile( file_path, - base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_READ | - base::PLATFORM_FILE_WRITE, + PLATFORM_FILE_CREATE | PLATFORM_FILE_READ | + PLATFORM_FILE_WRITE, NULL, NULL); - EXPECT_NE(base::kInvalidPlatformFileValue, file); + EXPECT_NE(kInvalidPlatformFileValue, file); // Write "test" to the file. char data_to_write[] = "test"; @@ -295,8 +295,8 @@ TEST(PlatformFile, TruncatePlatformFile) { // Extend the file. const int kExtendedFileLength = 10; int64 file_size = 0; - EXPECT_TRUE(base::TruncatePlatformFile(file, kExtendedFileLength)); - EXPECT_TRUE(file_util::GetFileSize(file_path, &file_size)); + EXPECT_TRUE(TruncatePlatformFile(file, kExtendedFileLength)); + EXPECT_TRUE(GetFileSize(file_path, &file_size)); EXPECT_EQ(kExtendedFileLength, file_size); // Make sure the file was zero-padded. @@ -310,8 +310,8 @@ TEST(PlatformFile, TruncatePlatformFile) { // Truncate the file. const int kTruncatedFileLength = 2; - EXPECT_TRUE(base::TruncatePlatformFile(file, kTruncatedFileLength)); - EXPECT_TRUE(file_util::GetFileSize(file_path, &file_size)); + EXPECT_TRUE(TruncatePlatformFile(file, kTruncatedFileLength)); + EXPECT_TRUE(GetFileSize(file_path, &file_size)); EXPECT_EQ(kTruncatedFileLength, file_size); // Make sure the file was truncated. @@ -321,7 +321,7 @@ TEST(PlatformFile, TruncatePlatformFile) { EXPECT_EQ(data_to_write[i], data_read[i]); // Close the file handle to allow the temp directory to be deleted. - base::ClosePlatformFile(file); + ClosePlatformFile(file); } // Flakily fails: http://crbug.com/86494 @@ -330,30 +330,30 @@ TEST(PlatformFile, TouchGetInfoPlatformFile) { #else TEST(PlatformFile, DISABLED_TouchGetInfoPlatformFile) { #endif - base::ScopedTempDir temp_dir; + ScopedTempDir temp_dir; ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); - base::PlatformFile file = base::CreatePlatformFile( + PlatformFile file = CreatePlatformFile( temp_dir.path().AppendASCII("touch_get_info_file"), - base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE | - base::PLATFORM_FILE_WRITE_ATTRIBUTES, + PLATFORM_FILE_CREATE | PLATFORM_FILE_WRITE | + PLATFORM_FILE_WRITE_ATTRIBUTES, NULL, NULL); - EXPECT_NE(base::kInvalidPlatformFileValue, file); + EXPECT_NE(kInvalidPlatformFileValue, file); // Get info for a newly created file. - base::PlatformFileInfo info; - EXPECT_TRUE(base::GetPlatformFileInfo(file, &info)); + PlatformFileInfo info; + EXPECT_TRUE(GetPlatformFileInfo(file, &info)); // Add 2 seconds to account for possible rounding errors on // filesystems that use a 1s or 2s timestamp granularity. - base::Time now = base::Time::Now() + base::TimeDelta::FromSeconds(2); + Time now = Time::Now() + TimeDelta::FromSeconds(2); EXPECT_EQ(0, info.size); EXPECT_FALSE(info.is_directory); EXPECT_FALSE(info.is_symbolic_link); EXPECT_LE(info.last_accessed.ToInternalValue(), now.ToInternalValue()); EXPECT_LE(info.last_modified.ToInternalValue(), now.ToInternalValue()); EXPECT_LE(info.creation_time.ToInternalValue(), now.ToInternalValue()); - base::Time creation_time = info.creation_time; + Time creation_time = info.creation_time; // Write "test" to the file. char data[] = "test"; @@ -365,16 +365,15 @@ TEST(PlatformFile, DISABLED_TouchGetInfoPlatformFile) { // It's best to add values that are multiples of 2 (in seconds) // to the current last_accessed and last_modified times, because // FATxx uses a 2s timestamp granularity. - base::Time new_last_accessed = - info.last_accessed + base::TimeDelta::FromSeconds(234); - base::Time new_last_modified = - info.last_modified + base::TimeDelta::FromMinutes(567); + Time new_last_accessed = + info.last_accessed + TimeDelta::FromSeconds(234); + Time new_last_modified = + info.last_modified + TimeDelta::FromMinutes(567); - EXPECT_TRUE(base::TouchPlatformFile(file, new_last_accessed, - new_last_modified)); + EXPECT_TRUE(TouchPlatformFile(file, new_last_accessed, new_last_modified)); // Make sure the file info was updated accordingly. - EXPECT_TRUE(base::GetPlatformFileInfo(file, &info)); + EXPECT_TRUE(GetPlatformFileInfo(file, &info)); EXPECT_EQ(info.size, kTestDataSize); EXPECT_FALSE(info.is_directory); EXPECT_FALSE(info.is_symbolic_link); @@ -396,5 +395,40 @@ TEST(PlatformFile, DISABLED_TouchGetInfoPlatformFile) { creation_time.ToInternalValue()); // Close the file handle to allow the temp directory to be deleted. - base::ClosePlatformFile(file); + ClosePlatformFile(file); } + +TEST(PlatformFile, ReadFileAtCurrentPosition) { + ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + FilePath file_path = + temp_dir.path().AppendASCII("read_file_at_current_position"); + PlatformFile file = CreatePlatformFile( + file_path, + PLATFORM_FILE_CREATE | PLATFORM_FILE_READ | + PLATFORM_FILE_WRITE, + NULL, NULL); + EXPECT_NE(kInvalidPlatformFileValue, file); + + const char kData[] = "test"; + const int kDataSize = arraysize(kData) - 1; + EXPECT_EQ(kDataSize, WriteFully(file, 0, kData, kDataSize)); + + EXPECT_EQ(0, SeekPlatformFile(file, PLATFORM_FILE_FROM_BEGIN, 0)); + + char buffer[kDataSize]; + int first_chunk_size = kDataSize / 2; + EXPECT_EQ(first_chunk_size, + ReadPlatformFileAtCurrentPos( + file, buffer, first_chunk_size)); + EXPECT_EQ(kDataSize - first_chunk_size, + ReadPlatformFileAtCurrentPos( + file, buffer + first_chunk_size, + kDataSize - first_chunk_size)); + EXPECT_EQ(std::string(buffer, buffer + kDataSize), + std::string(kData)); + + ClosePlatformFile(file); +} + +} // namespace base diff --git a/chromium/base/platform_file_win.cc b/chromium/base/platform_file_win.cc index c5b49fa7e88..07b5c48c22b 100644 --- a/chromium/base/platform_file_win.cc +++ b/chromium/base/platform_file_win.cc @@ -134,7 +134,7 @@ int64 SeekPlatformFile(PlatformFile file, int ReadPlatformFile(PlatformFile file, int64 offset, char* data, int size) { base::ThreadRestrictions::AssertIOAllowed(); - if (file == kInvalidPlatformFileValue) + if (file == kInvalidPlatformFileValue || size < 0) return -1; LARGE_INTEGER offset_li; @@ -147,14 +147,24 @@ int ReadPlatformFile(PlatformFile file, int64 offset, char* data, int size) { DWORD bytes_read; if (::ReadFile(file, data, size, &bytes_read, &overlapped) != 0) return bytes_read; - else if (ERROR_HANDLE_EOF == GetLastError()) + if (ERROR_HANDLE_EOF == GetLastError()) return 0; return -1; } int ReadPlatformFileAtCurrentPos(PlatformFile file, char* data, int size) { - return ReadPlatformFile(file, 0, data, size); + base::ThreadRestrictions::AssertIOAllowed(); + if (file == kInvalidPlatformFileValue || size < 0) + return -1; + + DWORD bytes_read; + if (::ReadFile(file, data, size, &bytes_read, NULL) != 0) + return bytes_read; + if (ERROR_HANDLE_EOF == GetLastError()) + return 0; + + return -1; } int ReadPlatformFileNoBestEffort(PlatformFile file, int64 offset, char* data, @@ -164,7 +174,7 @@ int ReadPlatformFileNoBestEffort(PlatformFile file, int64 offset, char* data, int ReadPlatformFileCurPosNoBestEffort(PlatformFile file, char* data, int size) { - return ReadPlatformFile(file, 0, data, size); + return ReadPlatformFileAtCurrentPos(file, data, size); } int WritePlatformFile(PlatformFile file, int64 offset, @@ -262,6 +272,20 @@ bool GetPlatformFileInfo(PlatformFile file, PlatformFileInfo* info) { return true; } +PlatformFileError LockPlatformFile(PlatformFile file) { + BOOL result = LockFile(file, 0, 0, MAXDWORD, MAXDWORD); + if (!result) + return LastErrorToPlatformFileError(GetLastError()); + return PLATFORM_FILE_OK; +} + +PlatformFileError UnlockPlatformFile(PlatformFile file) { + BOOL result = UnlockFile(file, 0, 0, MAXDWORD, MAXDWORD); + if (!result) + return LastErrorToPlatformFileError(GetLastError()); + return PLATFORM_FILE_OK; +} + PlatformFileError LastErrorToPlatformFileError(DWORD last_error) { switch (last_error) { case ERROR_SHARING_VIOLATION: diff --git a/chromium/base/posix/eintr_wrapper.h b/chromium/base/posix/eintr_wrapper.h index 8e267523370..854c43a67cb 100644 --- a/chromium/base/posix/eintr_wrapper.h +++ b/chromium/base/posix/eintr_wrapper.h @@ -9,6 +9,9 @@ // caller will nonetheless see an EINTR in Debug builds. // // On Windows, this wrapper macro does nothing. +// +// Don't wrap close calls in HANDLE_EINTR. Use IGNORE_EINTR if the return +// value of close is significant. See http://crbug.com/269623. #ifndef BASE_POSIX_EINTR_WRAPPER_H_ #define BASE_POSIX_EINTR_WRAPPER_H_ @@ -20,6 +23,7 @@ #include <errno.h> #if defined(NDEBUG) + #define HANDLE_EINTR(x) ({ \ typeof(x) eintr_wrapper_result; \ do { \ @@ -42,9 +46,21 @@ #endif // NDEBUG +#define IGNORE_EINTR(x) ({ \ + typeof(x) eintr_wrapper_result; \ + do { \ + eintr_wrapper_result = (x); \ + if (eintr_wrapper_result == -1 && errno == EINTR) { \ + eintr_wrapper_result = 0; \ + } \ + } while (0); \ + eintr_wrapper_result; \ +}) + #else #define HANDLE_EINTR(x) (x) +#define IGNORE_EINTR(x) (x) #endif // OS_POSIX diff --git a/chromium/base/posix/file_descriptor_shuffle.cc b/chromium/base/posix/file_descriptor_shuffle.cc index b5b7339bdfa..7bc9e26eb58 100644 --- a/chromium/base/posix/file_descriptor_shuffle.cc +++ b/chromium/base/posix/file_descriptor_shuffle.cc @@ -89,7 +89,7 @@ bool FileDescriptorTableInjection::Move(int src, int dest) { } void FileDescriptorTableInjection::Close(int fd) { - int ret = HANDLE_EINTR(close(fd)); + int ret = IGNORE_EINTR(close(fd)); DPCHECK(ret == 0); } diff --git a/chromium/base/posix/unix_domain_socket_linux_unittest.cc b/chromium/base/posix/unix_domain_socket_linux_unittest.cc index 1343555b330..22bb172ad5b 100644 --- a/chromium/base/posix/unix_domain_socket_linux_unittest.cc +++ b/chromium/base/posix/unix_domain_socket_linux_unittest.cc @@ -45,7 +45,7 @@ TEST(UnixDomainSocketTest, SendRecvMsgAbortOnReplyFDClose) { ASSERT_EQ(1U, message_fds.size()); // Close the reply FD. - ASSERT_EQ(0, HANDLE_EINTR(close(message_fds.front()))); + ASSERT_EQ(0, IGNORE_EINTR(close(message_fds.front()))); // Check that the thread didn't get blocked. WaitableEvent event(false, false); @@ -63,7 +63,7 @@ TEST(UnixDomainSocketTest, SendRecvMsgAvoidsSIGPIPE) { int fds[2]; ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds)); file_util::ScopedFD scoped_fd1(&fds[1]); - ASSERT_EQ(0, HANDLE_EINTR(close(fds[0]))); + ASSERT_EQ(0, IGNORE_EINTR(close(fds[0]))); // Have the thread send a synchronous message via the socket. Unless the // message is sent with MSG_NOSIGNAL, this shall result in SIGPIPE. diff --git a/chromium/base/power_monitor/power_monitor_device_source.h b/chromium/base/power_monitor/power_monitor_device_source.h index 993956031c8..37b065a77c6 100644 --- a/chromium/base/power_monitor/power_monitor_device_source.h +++ b/chromium/base/power_monitor/power_monitor_device_source.h @@ -61,9 +61,6 @@ class BASE_EXPORT PowerMonitorDeviceSource : public PowerMonitorSource { ~PowerMessageWindow(); private: - void ProcessWmPowerBroadcastMessage(int event_id); - LRESULT CALLBACK WndProc(HWND hwnd, UINT message, - WPARAM wparam, LPARAM lparam); static LRESULT CALLBACK WndProcThunk(HWND hwnd, UINT message, WPARAM wparam, diff --git a/chromium/base/power_monitor/power_monitor_device_source_win.cc b/chromium/base/power_monitor/power_monitor_device_source_win.cc index 6f4c1319b32..6609bd0e9e0 100644 --- a/chromium/base/power_monitor/power_monitor_device_source_win.cc +++ b/chromium/base/power_monitor/power_monitor_device_source_win.cc @@ -17,6 +17,37 @@ namespace { const wchar_t kWindowClassName[] = L"Base_PowerMessageWindow"; +void ProcessWmPowerBroadcastMessage(WPARAM event_id) { + PowerMonitorSource::PowerEvent power_event; + switch (event_id) { + case PBT_APMPOWERSTATUSCHANGE: // The power status changed. + power_event = PowerMonitorSource::POWER_STATE_EVENT; + break; + case PBT_APMRESUMEAUTOMATIC: // Resume from suspend. + //case PBT_APMRESUMESUSPEND: // User-initiated resume from suspend. + // We don't notify for this latter event + // because if it occurs it is always sent as a + // second event after PBT_APMRESUMEAUTOMATIC. + power_event = PowerMonitorSource::RESUME_EVENT; + break; + case PBT_APMSUSPEND: // System has been suspended. + power_event = PowerMonitorSource::SUSPEND_EVENT; + break; + default: + return; + + // Other Power Events: + // PBT_APMBATTERYLOW - removed in Vista. + // PBT_APMOEMEVENT - removed in Vista. + // PBT_APMQUERYSUSPEND - removed in Vista. + // PBT_APMQUERYSUSPENDFAILED - removed in Vista. + // PBT_APMRESUMECRITICAL - removed in Vista. + // PBT_POWERSETTINGCHANGE - user changed the power settings. + } + + ProcessPowerEventHelper(power_event); +} + } // namespace // Function to query the system to see if it is currently running on @@ -52,8 +83,6 @@ PowerMonitorDeviceSource::PowerMessageWindow::PowerMessageWindow() message_hwnd_ = CreateWindowEx(WS_EX_NOACTIVATE, kWindowClassName, NULL, WS_POPUP, 0, 0, 0, 0, NULL, NULL, instance_, NULL); - SetWindowLongPtr(message_hwnd_, GWLP_USERDATA, - reinterpret_cast<LONG_PTR>(this)); } PowerMonitorDeviceSource::PowerMessageWindow::~PowerMessageWindow() { @@ -63,68 +92,19 @@ PowerMonitorDeviceSource::PowerMessageWindow::~PowerMessageWindow() { } } -void -PowerMonitorDeviceSource::PowerMessageWindow::ProcessWmPowerBroadcastMessage( - int event_id) { - PowerMonitorSource::PowerEvent power_event; - switch (event_id) { - case PBT_APMPOWERSTATUSCHANGE: // The power status changed. - power_event = PowerMonitorSource::POWER_STATE_EVENT; - break; - case PBT_APMRESUMEAUTOMATIC: // Resume from suspend. - //case PBT_APMRESUMESUSPEND: // User-initiated resume from suspend. - // We don't notify for this latter event - // because if it occurs it is always sent as a - // second event after PBT_APMRESUMEAUTOMATIC. - power_event = PowerMonitorSource::RESUME_EVENT; - break; - case PBT_APMSUSPEND: // System has been suspended. - power_event = PowerMonitorSource::SUSPEND_EVENT; - break; - default: - return; - - // Other Power Events: - // PBT_APMBATTERYLOW - removed in Vista. - // PBT_APMOEMEVENT - removed in Vista. - // PBT_APMQUERYSUSPEND - removed in Vista. - // PBT_APMQUERYSUSPENDFAILED - removed in Vista. - // PBT_APMRESUMECRITICAL - removed in Vista. - // PBT_POWERSETTINGCHANGE - user changed the power settings. - } - - ProcessPowerEventHelper(power_event); -} - -LRESULT CALLBACK PowerMonitorDeviceSource::PowerMessageWindow::WndProc( +// static +LRESULT CALLBACK PowerMonitorDeviceSource::PowerMessageWindow::WndProcThunk( HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { switch (message) { - case WM_POWERBROADCAST: { - DWORD power_event = static_cast<DWORD>(message); - ProcessWmPowerBroadcastMessage(power_event); + case WM_POWERBROADCAST: + ProcessWmPowerBroadcastMessage(wparam); return TRUE; - } default: - break; + return ::DefWindowProc(hwnd, message, wparam, lparam); } - return ::DefWindowProc(hwnd, message, wparam, lparam); -} - -// static -LRESULT CALLBACK PowerMonitorDeviceSource::PowerMessageWindow::WndProcThunk( - HWND hwnd, - UINT message, - WPARAM wparam, - LPARAM lparam) { - PowerMonitorDeviceSource::PowerMessageWindow* message_hwnd = - reinterpret_cast<PowerMonitorDeviceSource::PowerMessageWindow*>( - GetWindowLongPtr(hwnd, GWLP_USERDATA)); - if (message_hwnd) - return message_hwnd->WndProc(hwnd, message, wparam, lparam); - return ::DefWindowProc(hwnd, message, wparam, lparam); } } // namespace base diff --git a/chromium/base/prefs/json_pref_store.cc b/chromium/base/prefs/json_pref_store.cc index e230407043a..ad97b8459cb 100644 --- a/chromium/base/prefs/json_pref_store.cc +++ b/chromium/base/prefs/json_pref_store.cc @@ -217,14 +217,10 @@ void JsonPrefStore::SetValueSilently(const std::string& key, } void JsonPrefStore::RemoveValue(const std::string& key) { - if (prefs_->Remove(key, NULL)) + if (prefs_->RemovePath(key, NULL)) ReportValueChanged(key); } -void JsonPrefStore::MarkNeedsEmptyValue(const std::string& key) { - keys_need_empty_value_.insert(key); -} - bool JsonPrefStore::ReadOnly() const { return read_only_; } @@ -324,35 +320,7 @@ JsonPrefStore::~JsonPrefStore() { } bool JsonPrefStore::SerializeData(std::string* output) { - // TODO(tc): Do we want to prune webkit preferences that match the default - // value? JSONStringValueSerializer serializer(output); serializer.set_pretty_print(true); - scoped_ptr<base::DictionaryValue> copy( - prefs_->DeepCopyWithoutEmptyChildren()); - - // Iterates |keys_need_empty_value_| and if the key exists in |prefs_|, - // ensure its empty ListValue or DictonaryValue is preserved. - for (std::set<std::string>::const_iterator - it = keys_need_empty_value_.begin(); - it != keys_need_empty_value_.end(); - ++it) { - const std::string& key = *it; - - base::Value* value = NULL; - if (!prefs_->Get(key, &value)) - continue; - - if (value->IsType(base::Value::TYPE_LIST)) { - const base::ListValue* list = NULL; - if (value->GetAsList(&list) && list->empty()) - copy->Set(key, new base::ListValue); - } else if (value->IsType(base::Value::TYPE_DICTIONARY)) { - const base::DictionaryValue* dict = NULL; - if (value->GetAsDictionary(&dict) && dict->empty()) - copy->Set(key, new base::DictionaryValue); - } - } - - return serializer.Serialize(*(copy.get())); + return serializer.Serialize(*prefs_); } diff --git a/chromium/base/prefs/json_pref_store.h b/chromium/base/prefs/json_pref_store.h index 9e6c1821834..21fc8f95ac9 100644 --- a/chromium/base/prefs/json_pref_store.h +++ b/chromium/base/prefs/json_pref_store.h @@ -21,8 +21,8 @@ namespace base { class DictionaryValue; class FilePath; -class SequencedWorkerPool; class SequencedTaskRunner; +class SequencedWorkerPool; class Value; } @@ -58,7 +58,6 @@ class BASE_PREFS_EXPORT JsonPrefStore virtual void SetValueSilently(const std::string& key, base::Value* value) OVERRIDE; virtual void RemoveValue(const std::string& key) OVERRIDE; - virtual void MarkNeedsEmptyValue(const std::string& key) OVERRIDE; virtual bool ReadOnly() const OVERRIDE; virtual PrefReadError GetReadError() const OVERRIDE; virtual PrefReadError ReadPrefs() OVERRIDE; diff --git a/chromium/base/prefs/json_pref_store_unittest.cc b/chromium/base/prefs/json_pref_store_unittest.cc index 34e1b8a9ede..a26afd71375 100644 --- a/chromium/base/prefs/json_pref_store_unittest.cc +++ b/chromium/base/prefs/json_pref_store_unittest.cc @@ -216,6 +216,33 @@ TEST_F(JsonPrefStoreTest, BasicAsync) { pref_store.get(), input_file, data_dir_.AppendASCII("write.golden.json")); } +TEST_F(JsonPrefStoreTest, PreserveEmptyValues) { + FilePath pref_file = temp_dir_.path().AppendASCII("empty_values.json"); + + scoped_refptr<JsonPrefStore> pref_store = + new JsonPrefStore(pref_file, message_loop_.message_loop_proxy()); + + // Set some keys with empty values. + pref_store->SetValue("list", new base::ListValue); + pref_store->SetValue("dict", new base::DictionaryValue); + + // Write to file. + pref_store->CommitPendingWrite(); + MessageLoop::current()->RunUntilIdle(); + + // Reload. + pref_store = new JsonPrefStore(pref_file, message_loop_.message_loop_proxy()); + ASSERT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE, pref_store->ReadPrefs()); + ASSERT_FALSE(pref_store->ReadOnly()); + + // Check values. + const Value* result = NULL; + EXPECT_TRUE(pref_store->GetValue("list", &result)); + EXPECT_TRUE(ListValue().Equals(result)); + EXPECT_TRUE(pref_store->GetValue("dict", &result)); + EXPECT_TRUE(DictionaryValue().Equals(result)); +} + // Tests asynchronous reading of the file when there is no file. TEST_F(JsonPrefStoreTest, AsyncNonExistingFile) { base::FilePath bogus_input_file = data_dir_.AppendASCII("read.txt"); @@ -237,51 +264,4 @@ TEST_F(JsonPrefStoreTest, AsyncNonExistingFile) { EXPECT_FALSE(pref_store->ReadOnly()); } -TEST_F(JsonPrefStoreTest, NeedsEmptyValue) { - base::FilePath pref_file = temp_dir_.path().AppendASCII("write.json"); - - ASSERT_TRUE(base::CopyFile( - data_dir_.AppendASCII("read.need_empty_value.json"), - pref_file)); - - // Test that the persistent value can be loaded. - ASSERT_TRUE(PathExists(pref_file)); - scoped_refptr<JsonPrefStore> pref_store = - new JsonPrefStore(pref_file, message_loop_.message_loop_proxy().get()); - ASSERT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE, pref_store->ReadPrefs()); - ASSERT_FALSE(pref_store->ReadOnly()); - - // The JSON file looks like this: - // { - // "list": [ 1 ], - // "list_needs_empty_value": [ 2 ], - // "dict": { - // "dummy": true, - // }, - // "dict_needs_empty_value": { - // "dummy": true, - // }, - // } - - // Set flag to preserve empty values for the following keys. - pref_store->MarkNeedsEmptyValue("list_needs_empty_value"); - pref_store->MarkNeedsEmptyValue("dict_needs_empty_value"); - - // Set all keys to empty values. - pref_store->SetValue("list", new base::ListValue); - pref_store->SetValue("list_needs_empty_value", new base::ListValue); - pref_store->SetValue("dict", new base::DictionaryValue); - pref_store->SetValue("dict_needs_empty_value", new base::DictionaryValue); - - // Write to file. - pref_store->CommitPendingWrite(); - RunLoop().RunUntilIdle(); - - // Compare to expected output. - base::FilePath golden_output_file = - data_dir_.AppendASCII("write.golden.need_empty_value.json"); - ASSERT_TRUE(PathExists(golden_output_file)); - EXPECT_TRUE(TextContentsEqual(golden_output_file, pref_file)); -} - } // namespace base diff --git a/chromium/base/prefs/overlay_user_pref_store.cc b/chromium/base/prefs/overlay_user_pref_store.cc index 47668cca653..a708bb68363 100644 --- a/chromium/base/prefs/overlay_user_pref_store.cc +++ b/chromium/base/prefs/overlay_user_pref_store.cc @@ -93,11 +93,6 @@ void OverlayUserPrefStore::RemoveValue(const std::string& key) { ReportValueChanged(key); } -void OverlayUserPrefStore::MarkNeedsEmptyValue(const std::string& key) { - if (!ShallBeStoredInOverlay(key)) - underlay_->MarkNeedsEmptyValue(key); -} - bool OverlayUserPrefStore::ReadOnly() const { return false; } diff --git a/chromium/base/prefs/overlay_user_pref_store.h b/chromium/base/prefs/overlay_user_pref_store.h index 1895ac0e4dd..c9993b94a0a 100644 --- a/chromium/base/prefs/overlay_user_pref_store.h +++ b/chromium/base/prefs/overlay_user_pref_store.h @@ -44,7 +44,6 @@ class BASE_PREFS_EXPORT OverlayUserPrefStore : public PersistentPrefStore, virtual void SetValueSilently(const std::string& key, base::Value* value) OVERRIDE; virtual void RemoveValue(const std::string& key) OVERRIDE; - virtual void MarkNeedsEmptyValue(const std::string& key) OVERRIDE; virtual bool ReadOnly() const OVERRIDE; virtual PrefReadError GetReadError() const OVERRIDE; virtual PrefReadError ReadPrefs() OVERRIDE; diff --git a/chromium/base/prefs/persistent_pref_store.h b/chromium/base/prefs/persistent_pref_store.h index 0baf02ac891..811ebff7014 100644 --- a/chromium/base/prefs/persistent_pref_store.h +++ b/chromium/base/prefs/persistent_pref_store.h @@ -63,10 +63,6 @@ class BASE_PREFS_EXPORT PersistentPrefStore : public PrefStore { // Removes the value for |key|. virtual void RemoveValue(const std::string& key) = 0; - // Marks that the |key| with empty ListValue/DictionaryValue needs to be - // persisted. - virtual void MarkNeedsEmptyValue(const std::string& key) = 0; - // Whether the store is in a pseudo-read-only mode where changes are not // actually persisted to disk. This happens in some cases when there are // read errors during startup. diff --git a/chromium/base/prefs/pref_registry.cc b/chromium/base/prefs/pref_registry.cc index 31d788d2f38..9d6b05c8362 100644 --- a/chromium/base/prefs/pref_registry.cc +++ b/chromium/base/prefs/pref_registry.cc @@ -42,11 +42,6 @@ void PrefRegistry::SetDefaultPrefValue(const char* pref_name, defaults_->ReplaceDefaultValue(pref_name, make_scoped_ptr(value)); } -void PrefRegistry::SetRegistrationCallback( - const RegistrationCallback& callback) { - registration_callback_ = callback; -} - void PrefRegistry::RegisterPreference(const char* path, base::Value* default_value) { base::Value::Type orig_type = default_value->GetType(); @@ -57,7 +52,4 @@ void PrefRegistry::RegisterPreference(const char* path, "Trying to register a previously registered pref: " << path; defaults_->SetDefaultValue(path, make_scoped_ptr(default_value)); - - if (!registration_callback_.is_null()) - registration_callback_.Run(path, default_value); } diff --git a/chromium/base/prefs/pref_registry.h b/chromium/base/prefs/pref_registry.h index 6c7eac9f595..896db3ffa62 100644 --- a/chromium/base/prefs/pref_registry.h +++ b/chromium/base/prefs/pref_registry.h @@ -5,7 +5,6 @@ #ifndef BASE_PREFS_PREF_REGISTRY_H_ #define BASE_PREFS_PREF_REGISTRY_H_ -#include "base/callback.h" #include "base/memory/ref_counted.h" #include "base/prefs/base_prefs_export.h" #include "base/prefs/pref_value_map.h" @@ -29,7 +28,6 @@ class PrefStore; class BASE_PREFS_EXPORT PrefRegistry : public base::RefCounted<PrefRegistry> { public: typedef PrefValueMap::const_iterator const_iterator; - typedef base::Callback<void(const char*, base::Value*)> RegistrationCallback; PrefRegistry(); @@ -45,15 +43,6 @@ class BASE_PREFS_EXPORT PrefRegistry : public base::RefCounted<PrefRegistry> { // |pref_name| must be a previously registered preference. void SetDefaultPrefValue(const char* pref_name, base::Value* value); - // Exactly one callback can be set for registration. The callback - // will be invoked each time registration has been performed on this - // object. - // - // Calling this method after a callback has already been set will - // make the object forget the previous callback and use the new one - // instead. - void SetRegistrationCallback(const RegistrationCallback& callback); - protected: friend class base::RefCounted<PrefRegistry>; virtual ~PrefRegistry(); @@ -64,8 +53,6 @@ class BASE_PREFS_EXPORT PrefRegistry : public base::RefCounted<PrefRegistry> { scoped_refptr<DefaultPrefStore> defaults_; private: - RegistrationCallback registration_callback_; - DISALLOW_COPY_AND_ASSIGN(PrefRegistry); }; diff --git a/chromium/base/prefs/pref_service.cc b/chromium/base/prefs/pref_service.cc index 046af915b33..576043b5489 100644 --- a/chromium/base/prefs/pref_service.cc +++ b/chromium/base/prefs/pref_service.cc @@ -53,20 +53,12 @@ PrefService::PrefService( read_error_callback_(read_error_callback) { pref_notifier_->SetPrefService(this); - pref_registry_->SetRegistrationCallback( - base::Bind(&PrefService::AddRegisteredPreference, - base::Unretained(this))); - AddInitialPreferences(); - InitFromStorage(async); } PrefService::~PrefService() { DCHECK(CalledOnValidThread()); - // Remove our callback, setting a NULL one. - pref_registry_->SetRegistrationCallback(PrefRegistry::RegistrationCallback()); - // Reset pointers so accesses after destruction reliably crash. pref_value_store_.reset(); pref_registry_ = NULL; @@ -87,11 +79,6 @@ void PrefService::InitFromStorage(bool async) { } } -bool PrefService::ReloadPersistentPrefs() { - return user_pref_store_->ReadPrefs() == - PersistentPrefStore::PREF_READ_ERROR_NONE; -} - void PrefService::CommitPendingWrite() { DCHECK(CalledOnValidThread()); user_pref_store_->CommitPendingWrite(); @@ -177,16 +164,29 @@ bool PrefService::HasPrefPath(const char* path) const { return pref && !pref->IsDefaultValue(); } -base::DictionaryValue* PrefService::GetPreferenceValues() const { +scoped_ptr<base::DictionaryValue> PrefService::GetPreferenceValues() const { DCHECK(CalledOnValidThread()); - base::DictionaryValue* out = new base::DictionaryValue; + scoped_ptr<base::DictionaryValue> out(new base::DictionaryValue); PrefRegistry::const_iterator i = pref_registry_->begin(); for (; i != pref_registry_->end(); ++i) { const base::Value* value = GetPreferenceValue(i->first); DCHECK(value); out->Set(i->first, value->DeepCopy()); } - return out; + return out.Pass(); +} + +scoped_ptr<base::DictionaryValue> +PrefService::GetPreferenceValuesWithoutPathExpansion() const { + DCHECK(CalledOnValidThread()); + scoped_ptr<base::DictionaryValue> out(new base::DictionaryValue); + PrefRegistry::const_iterator i = pref_registry_->begin(); + for (; i != pref_registry_->end(); ++i) { + const base::Value* value = GetPreferenceValue(i->first); + DCHECK(value); + out->SetWithoutPathExpansion(i->first, value->DeepCopy()); + } + return out.Pass(); } const PrefService::Preference* PrefService::FindPreference( @@ -320,42 +320,6 @@ PrefRegistry* PrefService::DeprecatedGetPrefRegistry() { return pref_registry_.get(); } -void PrefService::AddInitialPreferences() { - for (PrefRegistry::const_iterator it = pref_registry_->begin(); - it != pref_registry_->end(); - ++it) { - AddRegisteredPreference(it->first.c_str(), it->second); - } -} - -// TODO(joi): Once MarkNeedsEmptyValue is gone, we can probably -// completely get rid of this method. There will be one difference in -// semantics; currently all registered preferences are stored right -// away in the prefs_map_, if we remove this they would be stored only -// opportunistically. -void PrefService::AddRegisteredPreference(const char* path, - base::Value* default_value) { - DCHECK(CalledOnValidThread()); - - // For ListValue and DictionaryValue with non empty default, empty value - // for |path| needs to be persisted in |user_pref_store_|. So that - // non empty default is not used when user sets an empty ListValue or - // DictionaryValue. - bool needs_empty_value = false; - base::Value::Type orig_type = default_value->GetType(); - if (orig_type == base::Value::TYPE_LIST) { - const base::ListValue* list = NULL; - if (default_value->GetAsList(&list) && !list->empty()) - needs_empty_value = true; - } else if (orig_type == base::Value::TYPE_DICTIONARY) { - const base::DictionaryValue* dict = NULL; - if (default_value->GetAsDictionary(&dict) && !dict->empty()) - needs_empty_value = true; - } - if (needs_empty_value) - user_pref_store_->MarkNeedsEmptyValue(path); -} - void PrefService::ClearPref(const char* path) { DCHECK(CalledOnValidThread()); diff --git a/chromium/base/prefs/pref_service.h b/chromium/base/prefs/pref_service.h index 8af042f40b7..186433c1b62 100644 --- a/chromium/base/prefs/pref_service.h +++ b/chromium/base/prefs/pref_service.h @@ -138,7 +138,7 @@ class BASE_PREFS_EXPORT PrefService : public base::NonThreadSafe { const PrefService* pref_service_; }; - // You may wish to use PrefServiceBuilder or one of its subclasses + // You may wish to use PrefServiceFactory or one of its subclasses // for simplified construction. PrefService( PrefNotifierImpl* pref_notifier, @@ -150,11 +150,6 @@ class BASE_PREFS_EXPORT PrefService : public base::NonThreadSafe { bool async); virtual ~PrefService(); - // Reloads the data from file. This should only be called when the importer - // is running during first run, and the main process may not change pref - // values while the importer process is running. Returns true on success. - bool ReloadPersistentPrefs(); - // Lands pending writes to disk. This should only be used if we need to save // immediately (basically, during shutdown). void CommitPendingWrite(); @@ -234,7 +229,18 @@ class BASE_PREFS_EXPORT PrefService : public base::NonThreadSafe { // Returns a dictionary with effective preference values. The ownership // is passed to the caller. - base::DictionaryValue* GetPreferenceValues() const; + scoped_ptr<base::DictionaryValue> GetPreferenceValues() const; + + // Returns a dictionary with effective preference values. Contrary to + // GetPreferenceValues(), the paths of registered preferences are not split on + // '.' characters. If a registered preference stores a dictionary, however, + // the hierarchical structure inside the preference will be preserved. + // For example, if "foo.bar" is a registered preference, the result could look + // like this: + // {"foo.bar": {"a": {"b": true}}}. + // The ownership is passed to the caller. + scoped_ptr<base::DictionaryValue> GetPreferenceValuesWithoutPathExpansion() + const; bool ReadOnly() const; @@ -252,20 +258,19 @@ class BASE_PREFS_EXPORT PrefService : public base::NonThreadSafe { // Returns the PrefRegistry object for this service. You should not // use this; the intent is for no registrations to take place after // PrefService has been constructed. + // + // Instead of using this method, the recommended approach is to + // register all preferences for a class Xyz up front in a static + // Xyz::RegisterPrefs function, which gets invoked early in the + // application's start-up, before a PrefService is created. + // + // As an example, prefs registration in Chrome is triggered by the + // functions chrome::RegisterPrefs (for global preferences) and + // chrome::RegisterProfilePrefs (for user-specific preferences) + // implemented in chrome/browser/prefs/browser_prefs.cc. PrefRegistry* DeprecatedGetPrefRegistry(); protected: - // Adds the registered preferences from the PrefRegistry instance - // passed to us at construction time. - void AddInitialPreferences(); - - // Updates local caches for a preference registered at |path|. The - // |default_value| must not be NULL as it determines the preference - // value's type. AddRegisteredPreference must not be called twice - // for the same path. - void AddRegisteredPreference(const char* path, - base::Value* default_value); - // The PrefNotifier handles registering and notifying preference observers. // It is created and owned by this PrefService. Subclasses may access it for // unit testing. diff --git a/chromium/base/prefs/pref_service_builder.cc b/chromium/base/prefs/pref_service_builder.cc deleted file mode 100644 index 16b4565663b..00000000000 --- a/chromium/base/prefs/pref_service_builder.cc +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "base/prefs/pref_service_builder.h" - -#include "base/bind.h" -#include "base/prefs/default_pref_store.h" -#include "base/prefs/json_pref_store.h" -#include "base/prefs/pref_notifier_impl.h" -#include "base/prefs/pref_service.h" - -#include "base/prefs/pref_value_store.h" - -namespace { - -// Do-nothing default implementation. -void DoNothingHandleReadError(PersistentPrefStore::PrefReadError error) { -} - -} // namespace - -PrefServiceBuilder::PrefServiceBuilder() { - ResetDefaultState(); -} - -PrefServiceBuilder::~PrefServiceBuilder() { -} - -PrefServiceBuilder& PrefServiceBuilder::WithManagedPrefs(PrefStore* store) { - managed_prefs_ = store; - return *this; -} - -PrefServiceBuilder& PrefServiceBuilder::WithSupervisedUserPrefs( - PrefStore* store) { - supervised_user_prefs_ = store; - return *this; -} - -PrefServiceBuilder& PrefServiceBuilder::WithExtensionPrefs(PrefStore* store) { - extension_prefs_ = store; - return *this; -} - -PrefServiceBuilder& PrefServiceBuilder::WithCommandLinePrefs(PrefStore* store) { - command_line_prefs_ = store; - return *this; -} - -PrefServiceBuilder& PrefServiceBuilder::WithUserPrefs( - PersistentPrefStore* store) { - user_prefs_ = store; - return *this; -} - -PrefServiceBuilder& PrefServiceBuilder::WithRecommendedPrefs(PrefStore* store) { - recommended_prefs_ = store; - return *this; -} - -PrefServiceBuilder& PrefServiceBuilder::WithReadErrorCallback( - const base::Callback<void(PersistentPrefStore::PrefReadError)>& - read_error_callback) { - read_error_callback_ = read_error_callback; - return *this; -} - -PrefServiceBuilder& PrefServiceBuilder::WithUserFilePrefs( - const base::FilePath& prefs_file, - base::SequencedTaskRunner* task_runner) { - user_prefs_ = new JsonPrefStore(prefs_file, task_runner); - return *this; -} - -PrefServiceBuilder& PrefServiceBuilder::WithAsync(bool async) { - async_ = async; - return *this; -} - -PrefService* PrefServiceBuilder::Create(PrefRegistry* pref_registry) { - PrefNotifierImpl* pref_notifier = new PrefNotifierImpl(); - PrefService* pref_service = - new PrefService(pref_notifier, - new PrefValueStore(managed_prefs_.get(), - supervised_user_prefs_.get(), - extension_prefs_.get(), - command_line_prefs_.get(), - user_prefs_.get(), - recommended_prefs_.get(), - pref_registry->defaults().get(), - pref_notifier), - user_prefs_.get(), - pref_registry, - read_error_callback_, - async_); - ResetDefaultState(); - return pref_service; -} - -void PrefServiceBuilder::ResetDefaultState() { - managed_prefs_ = NULL; - supervised_user_prefs_ = NULL; - extension_prefs_ = NULL; - command_line_prefs_ = NULL; - user_prefs_ = NULL; - recommended_prefs_ = NULL; - read_error_callback_ = base::Bind(&DoNothingHandleReadError); - async_ = false; -} diff --git a/chromium/base/prefs/pref_service_builder.h b/chromium/base/prefs/pref_service_builder.h deleted file mode 100644 index 7af43926233..00000000000 --- a/chromium/base/prefs/pref_service_builder.h +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef BASE_PREFS_PREF_SERVICE_BUILDER_H_ -#define BASE_PREFS_PREF_SERVICE_BUILDER_H_ - -#include "base/basictypes.h" -#include "base/callback.h" -#include "base/memory/ref_counted.h" -#include "base/prefs/base_prefs_export.h" -#include "base/prefs/persistent_pref_store.h" -#include "base/prefs/pref_registry.h" -#include "base/prefs/pref_store.h" - -class PrefService; - -namespace base { -class FilePath; -class SequencedTaskRunner; -} - -// A class that allows convenient building of PrefService. -class BASE_PREFS_EXPORT PrefServiceBuilder { - public: - PrefServiceBuilder(); - virtual ~PrefServiceBuilder(); - - // Functions for setting the various parameters of the PrefService to build. - // These take ownership of the |store| parameter. - PrefServiceBuilder& WithManagedPrefs(PrefStore* store); - PrefServiceBuilder& WithSupervisedUserPrefs(PrefStore* store); - PrefServiceBuilder& WithExtensionPrefs(PrefStore* store); - PrefServiceBuilder& WithCommandLinePrefs(PrefStore* store); - PrefServiceBuilder& WithUserPrefs(PersistentPrefStore* store); - PrefServiceBuilder& WithRecommendedPrefs(PrefStore* store); - - // Sets up error callback for the PrefService. A do-nothing default - // is provided if this is not called. - PrefServiceBuilder& WithReadErrorCallback( - const base::Callback<void(PersistentPrefStore::PrefReadError)>& - read_error_callback); - - // Specifies to use an actual file-backed user pref store. - PrefServiceBuilder& WithUserFilePrefs( - const base::FilePath& prefs_file, - base::SequencedTaskRunner* task_runner); - - PrefServiceBuilder& WithAsync(bool async); - - // Creates a PrefService object initialized with the parameters from - // this builder. - virtual PrefService* Create(PrefRegistry* registry); - - protected: - virtual void ResetDefaultState(); - - scoped_refptr<PrefStore> managed_prefs_; - scoped_refptr<PrefStore> supervised_user_prefs_; - scoped_refptr<PrefStore> extension_prefs_; - scoped_refptr<PrefStore> command_line_prefs_; - scoped_refptr<PersistentPrefStore> user_prefs_; - scoped_refptr<PrefStore> recommended_prefs_; - - base::Callback<void(PersistentPrefStore::PrefReadError)> read_error_callback_; - - // Defaults to false. - bool async_; - - private: - DISALLOW_COPY_AND_ASSIGN(PrefServiceBuilder); -}; - -#endif // BASE_PREFS_PREF_SERVICE_BUILDER_H_ diff --git a/chromium/base/prefs/pref_service_factory.cc b/chromium/base/prefs/pref_service_factory.cc new file mode 100644 index 00000000000..9c598530c1b --- /dev/null +++ b/chromium/base/prefs/pref_service_factory.cc @@ -0,0 +1,63 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/prefs/pref_service_factory.h" + +#include "base/bind.h" +#include "base/prefs/default_pref_store.h" +#include "base/prefs/json_pref_store.h" +#include "base/prefs/pref_notifier_impl.h" +#include "base/prefs/pref_service.h" + +#include "base/prefs/pref_value_store.h" + +namespace base { + +namespace { + +// Do-nothing default implementation. +void DoNothingHandleReadError(PersistentPrefStore::PrefReadError error) { +} + +} // namespace + +PrefServiceFactory::PrefServiceFactory() + : managed_prefs_(NULL), + supervised_user_prefs_(NULL), + extension_prefs_(NULL), + command_line_prefs_(NULL), + user_prefs_(NULL), + recommended_prefs_(NULL), + read_error_callback_(base::Bind(&DoNothingHandleReadError)), + async_(false) {} + +PrefServiceFactory::~PrefServiceFactory() {} + +void PrefServiceFactory::SetUserPrefsFile( + const base::FilePath& prefs_file, + base::SequencedTaskRunner* task_runner) { + user_prefs_ = new JsonPrefStore(prefs_file, task_runner); +} + +scoped_ptr<PrefService> PrefServiceFactory::Create( + PrefRegistry* pref_registry) { + PrefNotifierImpl* pref_notifier = new PrefNotifierImpl(); + scoped_ptr<PrefService> pref_service( + new PrefService(pref_notifier, + new PrefValueStore(managed_prefs_.get(), + supervised_user_prefs_.get(), + extension_prefs_.get(), + command_line_prefs_.get(), + user_prefs_.get(), + recommended_prefs_.get(), + pref_registry->defaults().get(), + pref_notifier), + user_prefs_.get(), + pref_registry, + read_error_callback_, + async_)); + return pref_service.Pass(); +} + +} // namespace base diff --git a/chromium/base/prefs/pref_service_factory.h b/chromium/base/prefs/pref_service_factory.h new file mode 100644 index 00000000000..ca608c2237b --- /dev/null +++ b/chromium/base/prefs/pref_service_factory.h @@ -0,0 +1,91 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_PREFS_PREF_SERVICE_FACTORY_H_ +#define BASE_PREFS_PREF_SERVICE_FACTORY_H_ + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "base/prefs/base_prefs_export.h" +#include "base/prefs/persistent_pref_store.h" +#include "base/prefs/pref_registry.h" +#include "base/prefs/pref_store.h" + +class PrefService; + +namespace base { + +class FilePath; +class SequencedTaskRunner; + +// A class that allows convenient building of PrefService. +class BASE_PREFS_EXPORT PrefServiceFactory { + public: + PrefServiceFactory(); + virtual ~PrefServiceFactory(); + + // Functions for setting the various parameters of the PrefService to build. + void set_managed_prefs(const scoped_refptr<PrefStore>& managed_prefs) { + managed_prefs_ = managed_prefs; + } + void set_supervised_user_prefs( + const scoped_refptr<PrefStore>& supervised_user_prefs) { + supervised_user_prefs_ = supervised_user_prefs; + } + void set_extension_prefs(const scoped_refptr<PrefStore>& extension_prefs) { + extension_prefs_ = extension_prefs; + } + void set_command_line_prefs( + const scoped_refptr<PrefStore>& command_line_prefs) { + command_line_prefs_ = command_line_prefs; + } + void set_user_prefs(const scoped_refptr<PersistentPrefStore>& user_prefs) { + user_prefs_ = user_prefs; + } + void set_recommended_prefs( + const scoped_refptr<PrefStore>& recommended_prefs) { + recommended_prefs_ = recommended_prefs; + } + + // Sets up error callback for the PrefService. A do-nothing default + // is provided if this is not called. + void set_read_error_callback( + const base::Callback<void(PersistentPrefStore::PrefReadError)>& + read_error_callback) { + read_error_callback_ = read_error_callback; + } + + // Specifies to use an actual file-backed user pref store. + void SetUserPrefsFile(const base::FilePath& prefs_file, + base::SequencedTaskRunner* task_runner); + + void set_async(bool async) { + async_ = async; + } + + // Creates a PrefService object initialized with the parameters from + // this factory. + scoped_ptr<PrefService> Create(PrefRegistry* registry); + + protected: + scoped_refptr<PrefStore> managed_prefs_; + scoped_refptr<PrefStore> supervised_user_prefs_; + scoped_refptr<PrefStore> extension_prefs_; + scoped_refptr<PrefStore> command_line_prefs_; + scoped_refptr<PersistentPrefStore> user_prefs_; + scoped_refptr<PrefStore> recommended_prefs_; + + base::Callback<void(PersistentPrefStore::PrefReadError)> read_error_callback_; + + // Defaults to false. + bool async_; + + private: + DISALLOW_COPY_AND_ASSIGN(PrefServiceFactory); +}; + +} // namespace base + +#endif // BASE_PREFS_PREF_SERVICE_FACTORY_H_ diff --git a/chromium/base/prefs/scoped_user_pref_update.cc b/chromium/base/prefs/scoped_user_pref_update.cc new file mode 100644 index 00000000000..c86b1634b52 --- /dev/null +++ b/chromium/base/prefs/scoped_user_pref_update.cc @@ -0,0 +1,36 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/prefs/scoped_user_pref_update.h" + +#include "base/logging.h" +#include "base/prefs/pref_notifier.h" +#include "base/prefs/pref_service.h" + +namespace subtle { + +ScopedUserPrefUpdateBase::ScopedUserPrefUpdateBase(PrefService* service, + const char* path) + : service_(service), + path_(path), + value_(NULL) {} + +ScopedUserPrefUpdateBase::~ScopedUserPrefUpdateBase() { + Notify(); +} + +Value* ScopedUserPrefUpdateBase::GetValueOfType(base::Value::Type type) { + if (!value_) + value_ = service_->GetMutableUserPref(path_.c_str(), type); + return value_; +} + +void ScopedUserPrefUpdateBase::Notify() { + if (value_) { + service_->ReportUserPrefChanged(path_); + value_ = NULL; + } +} + +} // namespace subtle diff --git a/chromium/base/prefs/scoped_user_pref_update.h b/chromium/base/prefs/scoped_user_pref_update.h new file mode 100644 index 00000000000..82d6739dd32 --- /dev/null +++ b/chromium/base/prefs/scoped_user_pref_update.h @@ -0,0 +1,108 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// A helper class that assists preferences in firing notifications when lists +// or dictionaries are changed. + +#ifndef BASE_PREFS_SCOPED_USER_PREF_UPDATE_H_ +#define BASE_PREFS_SCOPED_USER_PREF_UPDATE_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/prefs/base_prefs_export.h" +#include "base/prefs/pref_service.h" +#include "base/threading/non_thread_safe.h" +#include "base/values.h" + +class PrefService; + +namespace base { +class DictionaryValue; +class ListValue; +} + +namespace subtle { + +// Base class for ScopedUserPrefUpdateTemplate that contains the parts +// that do not depend on ScopedUserPrefUpdateTemplate's template parameter. +// +// We need this base class mostly for making it a friend of PrefService +// and getting access to PrefService::GetMutableUserPref and +// PrefService::ReportUserPrefChanged. +class BASE_PREFS_EXPORT ScopedUserPrefUpdateBase : public base::NonThreadSafe { + protected: + ScopedUserPrefUpdateBase(PrefService* service, const char* path); + + // Calls Notify(). + ~ScopedUserPrefUpdateBase(); + + // Sets |value_| to |service_|->GetMutableUserPref and returns it. + base::Value* GetValueOfType(base::Value::Type type); + + private: + // If |value_| is not null, triggers a notification of PrefObservers and + // resets |value_|. + void Notify(); + + // Weak pointer. + PrefService* service_; + // Path of the preference being updated. + std::string path_; + // Cache of value from user pref store (set between Get() and Notify() calls). + base::Value* value_; + + DISALLOW_COPY_AND_ASSIGN(ScopedUserPrefUpdateBase); +}; + +} // namespace subtle + +// Class to support modifications to DictionaryValues and ListValues while +// guaranteeing that PrefObservers are notified of changed values. +// +// This class may only be used on the UI thread as it requires access to the +// PrefService. +template <typename T, base::Value::Type type_enum_value> +class ScopedUserPrefUpdate : public subtle::ScopedUserPrefUpdateBase { + public: + ScopedUserPrefUpdate(PrefService* service, const char* path) + : ScopedUserPrefUpdateBase(service, path) {} + + // Triggers an update notification if Get() was called. + virtual ~ScopedUserPrefUpdate() {} + + // Returns a mutable |T| instance that + // - is already in the user pref store, or + // - is (silently) created and written to the user pref store if none existed + // before. + // + // Calling Get() implies that an update notification is necessary at + // destruction time. + // + // The ownership of the return value remains with the user pref store. + // Virtual so it can be overriden in subclasses that transform the value + // before returning it (for example to return a subelement of a dictionary). + virtual T* Get() { + return static_cast<T*>(GetValueOfType(type_enum_value)); + } + + T& operator*() { + return *Get(); + } + + T* operator->() { + return Get(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(ScopedUserPrefUpdate); +}; + +typedef ScopedUserPrefUpdate<base::DictionaryValue, + base::Value::TYPE_DICTIONARY> + DictionaryPrefUpdate; +typedef ScopedUserPrefUpdate<base::ListValue, base::Value::TYPE_LIST> + ListPrefUpdate; + +#endif // BASE_PREFS_SCOPED_USER_PREF_UPDATE_H_ diff --git a/chromium/base/prefs/scoped_user_pref_update_unittest.cc b/chromium/base/prefs/scoped_user_pref_update_unittest.cc new file mode 100644 index 00000000000..505526c07cf --- /dev/null +++ b/chromium/base/prefs/scoped_user_pref_update_unittest.cc @@ -0,0 +1,81 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/prefs/mock_pref_change_callback.h" +#include "base/prefs/pref_change_registrar.h" +#include "base/prefs/pref_registry_simple.h" +#include "base/prefs/scoped_user_pref_update.h" +#include "base/prefs/testing_pref_service.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::_; +using testing::Mock; + +class ScopedUserPrefUpdateTest : public testing::Test { + public: + ScopedUserPrefUpdateTest() : observer_(&prefs_) {} + virtual ~ScopedUserPrefUpdateTest() {} + + protected: + virtual void SetUp() { + prefs_.registry()->RegisterDictionaryPref(kPref); + registrar_.Init(&prefs_); + registrar_.Add(kPref, observer_.GetCallback()); + } + + static const char kPref[]; + static const char kKey[]; + static const char kValue[]; + + TestingPrefServiceSimple prefs_; + MockPrefChangeCallback observer_; + PrefChangeRegistrar registrar_; +}; + +const char ScopedUserPrefUpdateTest::kPref[] = "name"; +const char ScopedUserPrefUpdateTest::kKey[] = "key"; +const char ScopedUserPrefUpdateTest::kValue[] = "value"; + +TEST_F(ScopedUserPrefUpdateTest, RegularUse) { + // Dictionary that will be expected to be set at the end. + DictionaryValue expected_dictionary; + expected_dictionary.SetString(kKey, kValue); + + { + EXPECT_CALL(observer_, OnPreferenceChanged(_)).Times(0); + DictionaryPrefUpdate update(&prefs_, kPref); + DictionaryValue* value = update.Get(); + ASSERT_TRUE(value); + value->SetString(kKey, kValue); + + // The dictionary was created for us but the creation should have happened + // silently without notifications. + Mock::VerifyAndClearExpectations(&observer_); + + // Modifications happen online and are instantly visible, though. + const DictionaryValue* current_value = prefs_.GetDictionary(kPref); + ASSERT_TRUE(current_value); + EXPECT_TRUE(expected_dictionary.Equals(current_value)); + + // Now we are leaving the scope of the update so we should be notified. + observer_.Expect(kPref, &expected_dictionary); + } + Mock::VerifyAndClearExpectations(&observer_); + + const DictionaryValue* current_value = prefs_.GetDictionary(kPref); + ASSERT_TRUE(current_value); + EXPECT_TRUE(expected_dictionary.Equals(current_value)); +} + +TEST_F(ScopedUserPrefUpdateTest, NeverTouchAnything) { + const DictionaryValue* old_value = prefs_.GetDictionary(kPref); + EXPECT_CALL(observer_, OnPreferenceChanged(_)).Times(0); + { + DictionaryPrefUpdate update(&prefs_, kPref); + } + const DictionaryValue* new_value = prefs_.GetDictionary(kPref); + EXPECT_EQ(old_value, new_value); + Mock::VerifyAndClearExpectations(&observer_); +} diff --git a/chromium/base/prefs/testing_pref_service.h b/chromium/base/prefs/testing_pref_service.h index 1af4ba6d1e1..a9ab937429b 100644 --- a/chromium/base/prefs/testing_pref_service.h +++ b/chromium/base/prefs/testing_pref_service.h @@ -90,7 +90,7 @@ class TestingPrefServiceSimple // an existing TestingPrefServiceSimple instance. On a production // PrefService you would do all registrations before constructing // it, passing it a PrefRegistry via its constructor (or via - // e.g. PrefServiceBuilder). + // e.g. PrefServiceFactory). PrefRegistrySimple* registry(); private: diff --git a/chromium/base/prefs/testing_pref_store.cc b/chromium/base/prefs/testing_pref_store.cc index b4209695a6b..2f429c9a1a9 100644 --- a/chromium/base/prefs/testing_pref_store.cc +++ b/chromium/base/prefs/testing_pref_store.cc @@ -53,9 +53,6 @@ void TestingPrefStore::RemoveValue(const std::string& key) { NotifyPrefValueChanged(key); } -void TestingPrefStore::MarkNeedsEmptyValue(const std::string& key) { -} - bool TestingPrefStore::ReadOnly() const { return read_only_; } diff --git a/chromium/base/prefs/testing_pref_store.h b/chromium/base/prefs/testing_pref_store.h index 08d7125e241..c6a2b8336f0 100644 --- a/chromium/base/prefs/testing_pref_store.h +++ b/chromium/base/prefs/testing_pref_store.h @@ -36,7 +36,6 @@ class TestingPrefStore : public PersistentPrefStore { virtual void SetValueSilently(const std::string& key, base::Value* value) OVERRIDE; virtual void RemoveValue(const std::string& key) OVERRIDE; - virtual void MarkNeedsEmptyValue(const std::string& key) OVERRIDE; virtual bool ReadOnly() const OVERRIDE; virtual PrefReadError GetReadError() const OVERRIDE; virtual PersistentPrefStore::PrefReadError ReadPrefs() OVERRIDE; diff --git a/chromium/base/process/internal_linux.h b/chromium/base/process/internal_linux.h index a10cee36e56..0cacd3f3a84 100644 --- a/chromium/base/process/internal_linux.h +++ b/chromium/base/process/internal_linux.h @@ -8,6 +8,8 @@ #ifndef BASE_PROCESS_LINUX_INTERNAL_H_ #define BASE_PROCESS_LINUX_INTERNAL_H_ +#include <unistd.h> + #include "base/files/file_path.h" namespace base { diff --git a/chromium/base/process/kill.h b/chromium/base/process/kill.h index c828c718def..de72d7a8dca 100644 --- a/chromium/base/process/kill.h +++ b/chromium/base/process/kill.h @@ -25,6 +25,13 @@ enum TerminationStatus { TERMINATION_STATUS_PROCESS_WAS_KILLED, // e.g. SIGKILL or task manager kill TERMINATION_STATUS_PROCESS_CRASHED, // e.g. Segmentation fault TERMINATION_STATUS_STILL_RUNNING, // child hasn't exited yet +#if defined(OS_ANDROID) + // On Android processes are spawned from the system Zygote and we do not get + // the termination status. We can't know if the termination was a crash or an + // oom kill for sure, but we can use status of the strong process bindings as + // a hint. + TERMINATION_STATUS_OOM_PROTECTED, // child was protected from oom kill +#endif TERMINATION_STATUS_MAX_ENUM }; diff --git a/chromium/base/process/launch.cc b/chromium/base/process/launch.cc index 1329a5af23e..0c9f3a88fef 100644 --- a/chromium/base/process/launch.cc +++ b/chromium/base/process/launch.cc @@ -10,6 +10,7 @@ LaunchOptions::LaunchOptions() : wait(false), #if defined(OS_WIN) start_hidden(false), + handles_to_inherit(NULL), inherit_handles(false), as_user(NULL), empty_desktop_name(false), diff --git a/chromium/base/process/launch.h b/chromium/base/process/launch.h index ac2df5eee5f..336bfba16e9 100644 --- a/chromium/base/process/launch.h +++ b/chromium/base/process/launch.h @@ -1,4 +1,4 @@ -// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -16,17 +16,23 @@ #include "base/basictypes.h" #include "base/environment.h" #include "base/process/process_handle.h" +#include "base/strings/string_piece.h" #if defined(OS_POSIX) #include "base/posix/file_descriptor_shuffle.h" #elif defined(OS_WIN) #include <windows.h> +#include "base/win/scoped_handle.h" #endif class CommandLine; namespace base { +#if defined(OS_WIN) +typedef std::vector<HANDLE> HandlesToInheritVector; +#endif +// TODO(viettrungluu): Only define this on POSIX? typedef std::vector<std::pair<int, int> > FileHandleMappingVector; // Options for launching a subprocess that are passed to LaunchProcess(). @@ -41,13 +47,19 @@ struct BASE_EXPORT LaunchOptions { #if defined(OS_WIN) bool start_hidden; + // If non-null, inherit exactly the list of handles in this vector (these + // handles must be inheritable). This is only supported on Vista and higher. + HandlesToInheritVector* handles_to_inherit; + // If true, the new process inherits handles from the parent. In production // code this flag should be used only when running short-lived, trusted // binaries, because open handles from other libraries and subsystems will // leak to the child process, causing errors such as open socket hangs. + // Note: If |handles_to_inherit| is non-null, this flag is ignored and only + // those handles will be inherited (on Vista and higher). bool inherit_handles; - // If non-NULL, runs as if the user represented by the token had launched it. + // If non-null, runs as if the user represented by the token had launched it. // Whether the application is visible on the interactive desktop depends on // the token belonging to an interactive logon session. // @@ -59,7 +71,7 @@ struct BASE_EXPORT LaunchOptions { // If true, use an empty string for the desktop name. bool empty_desktop_name; - // If non-NULL, launches the application in that job object. The process will + // If non-null, launches the application in that job object. The process will // be terminated immediately and LaunchProcess() will fail if assignment to // the job object fails. HANDLE job_handle; @@ -81,7 +93,7 @@ struct BASE_EXPORT LaunchOptions { // the same environment. See AlterEnvironment(). EnvironmentMap environ; - // If non-NULL, remap file descriptors according to the mapping of + // If non-null, remap file descriptors according to the mapping of // src fd->dest fd to propagate FDs into the child process. // This pointer is owned by the caller and must live through the // call to LaunchProcess(). @@ -116,7 +128,7 @@ struct BASE_EXPORT LaunchOptions { // // Returns true upon success. // -// Upon success, if |process_handle| is non-NULL, it will be filled in with the +// Upon success, if |process_handle| is non-null, it will be filled in with the // handle of the launched process. NOTE: In this case, the caller is // responsible for closing the handle so that it doesn't leak! // Otherwise, the process handle will be implicitly closed. @@ -146,7 +158,7 @@ BASE_EXPORT bool LaunchProcess(const CommandLine& cmdline, // cmdline = "c:\windows\explorer.exe" -foo "c:\bar\" BASE_EXPORT bool LaunchProcess(const string16& cmdline, const LaunchOptions& options, - ProcessHandle* process_handle); + win::ScopedHandle* process_handle); #elif defined(OS_POSIX) // A POSIX-specific version of LaunchProcess that takes an argv array @@ -179,6 +191,13 @@ BASE_EXPORT void RouteStdioToConsole(); // indicating success). BASE_EXPORT bool GetAppOutput(const CommandLine& cl, std::string* output); +#if defined(OS_WIN) +// A Windows-specific version of GetAppOutput that takes a command line string +// instead of a CommandLine object. Useful for situations where you need to +// control the command line arguments directly. +BASE_EXPORT bool GetAppOutput(const StringPiece16& cl, std::string* output); +#endif + #if defined(OS_POSIX) // A POSIX-specific version of GetAppOutput that takes an argv array // instead of a CommandLine. Useful for situations where you need to diff --git a/chromium/base/process/launch_posix.cc b/chromium/base/process/launch_posix.cc index de6286da26b..8dc8f9e215d 100644 --- a/chromium/base/process/launch_posix.cc +++ b/chromium/base/process/launch_posix.cc @@ -230,7 +230,7 @@ void CloseSuperfluousFds(const base::InjectiveMultimap& saved_mapping) { // Since we're just trying to close anything we can find, // ignore any error return values of close(). - ignore_result(HANDLE_EINTR(close(fd))); + close(fd); } return; } @@ -264,7 +264,7 @@ void CloseSuperfluousFds(const base::InjectiveMultimap& saved_mapping) { // these FDs are >= |max_fds|, so we can check against that here // before closing. See https://bugs.kde.org/show_bug.cgi?id=191758 if (fd < static_cast<int>(max_fds)) { - int ret = HANDLE_EINTR(close(fd)); + int ret = IGNORE_EINTR(close(fd)); DPCHECK(ret == 0); } } diff --git a/chromium/base/process/launch_win.cc b/chromium/base/process/launch_win.cc index da913efba74..0c831cf7b4a 100644 --- a/chromium/base/process/launch_win.cc +++ b/chromium/base/process/launch_win.cc @@ -11,6 +11,7 @@ #include <psapi.h> #include <ios> +#include <limits> #include "base/bind.h" #include "base/bind_helpers.h" @@ -25,6 +26,7 @@ #include "base/win/object_watcher.h" #include "base/win/scoped_handle.h" #include "base/win/scoped_process_information.h" +#include "base/win/startup_information.h" #include "base/win/windows_version.h" // userenv.dll is required for CreateEnvironmentBlock(). @@ -103,27 +105,62 @@ void RouteStdioToConsole() { bool LaunchProcess(const string16& cmdline, const LaunchOptions& options, - ProcessHandle* process_handle) { - STARTUPINFO startup_info = {}; - startup_info.cb = sizeof(startup_info); + win::ScopedHandle* process_handle) { + win::StartupInformation startup_info_wrapper; + STARTUPINFO* startup_info = startup_info_wrapper.startup_info(); + + bool inherit_handles = options.inherit_handles; + DWORD flags = 0; + if (options.handles_to_inherit) { + if (options.handles_to_inherit->empty()) { + inherit_handles = false; + } else { + if (base::win::GetVersion() < base::win::VERSION_VISTA) { + DLOG(ERROR) << "Specifying handles to inherit requires Vista or later."; + return false; + } + + if (options.handles_to_inherit->size() > + std::numeric_limits<DWORD>::max() / sizeof(HANDLE)) { + DLOG(ERROR) << "Too many handles to inherit."; + return false; + } + + if (!startup_info_wrapper.InitializeProcThreadAttributeList(1)) { + DPLOG(ERROR); + return false; + } + + if (!startup_info_wrapper.UpdateProcThreadAttribute( + PROC_THREAD_ATTRIBUTE_HANDLE_LIST, + const_cast<HANDLE*>(&options.handles_to_inherit->at(0)), + static_cast<DWORD>(options.handles_to_inherit->size() * + sizeof(HANDLE)))) { + DPLOG(ERROR); + return false; + } + + inherit_handles = true; + flags |= EXTENDED_STARTUPINFO_PRESENT; + } + } + if (options.empty_desktop_name) - startup_info.lpDesktop = L""; - startup_info.dwFlags = STARTF_USESHOWWINDOW; - startup_info.wShowWindow = options.start_hidden ? SW_HIDE : SW_SHOW; + startup_info->lpDesktop = L""; + startup_info->dwFlags = STARTF_USESHOWWINDOW; + startup_info->wShowWindow = options.start_hidden ? SW_HIDE : SW_SHOW; if (options.stdin_handle || options.stdout_handle || options.stderr_handle) { - DCHECK(options.inherit_handles); + DCHECK(inherit_handles); DCHECK(options.stdin_handle); DCHECK(options.stdout_handle); DCHECK(options.stderr_handle); - startup_info.dwFlags |= STARTF_USESTDHANDLES; - startup_info.hStdInput = options.stdin_handle; - startup_info.hStdOutput = options.stdout_handle; - startup_info.hStdError = options.stderr_handle; + startup_info->dwFlags |= STARTF_USESTDHANDLES; + startup_info->hStdInput = options.stdin_handle; + startup_info->hStdOutput = options.stdout_handle; + startup_info->hStdError = options.stderr_handle; } - DWORD flags = 0; - if (options.job_handle) { flags |= CREATE_SUSPENDED; @@ -136,7 +173,7 @@ bool LaunchProcess(const string16& cmdline, if (options.force_breakaway_from_job_) flags |= CREATE_BREAKAWAY_FROM_JOB; - base::win::ScopedProcessInformation process_info; + PROCESS_INFORMATION temp_process_info = {}; if (options.as_user) { flags |= CREATE_UNICODE_ENVIRONMENT; @@ -150,9 +187,9 @@ bool LaunchProcess(const string16& cmdline, BOOL launched = CreateProcessAsUser(options.as_user, NULL, const_cast<wchar_t*>(cmdline.c_str()), - NULL, NULL, options.inherit_handles, flags, - enviroment_block, NULL, &startup_info, - process_info.Receive()); + NULL, NULL, inherit_handles, flags, + enviroment_block, NULL, startup_info, + &temp_process_info); DestroyEnvironmentBlock(enviroment_block); if (!launched) { DPLOG(ERROR); @@ -161,12 +198,13 @@ bool LaunchProcess(const string16& cmdline, } else { if (!CreateProcess(NULL, const_cast<wchar_t*>(cmdline.c_str()), NULL, NULL, - options.inherit_handles, flags, NULL, NULL, - &startup_info, process_info.Receive())) { + inherit_handles, flags, NULL, NULL, + startup_info, &temp_process_info)) { DPLOG(ERROR); return false; } } + base::win::ScopedProcessInformation process_info(temp_process_info); if (options.job_handle) { if (0 == AssignProcessToJobObject(options.job_handle, @@ -184,7 +222,7 @@ bool LaunchProcess(const string16& cmdline, // If the caller wants the process handle, we won't close it. if (process_handle) - *process_handle = process_info.TakeProcessHandle(); + process_handle->Set(process_info.TakeProcessHandle()); return true; } @@ -192,7 +230,13 @@ bool LaunchProcess(const string16& cmdline, bool LaunchProcess(const CommandLine& cmdline, const LaunchOptions& options, ProcessHandle* process_handle) { - return LaunchProcess(cmdline.GetCommandLineString(), options, process_handle); + if (!process_handle) + return LaunchProcess(cmdline.GetCommandLineString(), options, NULL); + + win::ScopedHandle process; + bool rv = LaunchProcess(cmdline.GetCommandLineString(), options, &process); + *process_handle = process.Take(); + return rv; } bool SetJobObjectLimitFlags(HANDLE job_object, DWORD limit_flags) { @@ -206,6 +250,10 @@ bool SetJobObjectLimitFlags(HANDLE job_object, DWORD limit_flags) { } bool GetAppOutput(const CommandLine& cl, std::string* output) { + return GetAppOutput(cl.GetCommandLineString(), output); +} + +bool GetAppOutput(const StringPiece16& cl, std::string* output) { HANDLE out_read = NULL; HANDLE out_write = NULL; @@ -231,10 +279,10 @@ bool GetAppOutput(const CommandLine& cl, std::string* output) { return false; } - FilePath::StringType writable_command_line_string(cl.GetCommandLineString()); + FilePath::StringType writable_command_line_string; + writable_command_line_string.assign(cl.data(), cl.size()); - base::win::ScopedProcessInformation proc_info; - STARTUPINFO start_info = { 0 }; + STARTUPINFO start_info = {}; start_info.cb = sizeof(STARTUPINFO); start_info.hStdOutput = out_write; @@ -244,14 +292,16 @@ bool GetAppOutput(const CommandLine& cl, std::string* output) { start_info.dwFlags |= STARTF_USESTDHANDLES; // Create the child process. + PROCESS_INFORMATION temp_process_info = {}; if (!CreateProcess(NULL, &writable_command_line_string[0], NULL, NULL, TRUE, // Handles are inherited. - 0, NULL, NULL, &start_info, proc_info.Receive())) { + 0, NULL, NULL, &start_info, &temp_process_info)) { NOTREACHED() << "Failed to start process"; return false; } + base::win::ScopedProcessInformation proc_info(temp_process_info); // Close our writing end of pipe now. Otherwise later read would not be able // to detect end of child's output. diff --git a/chromium/base/process/memory.h b/chromium/base/process/memory.h index de79477e95d..e6696cb8a70 100644 --- a/chromium/base/process/memory.h +++ b/chromium/base/process/memory.h @@ -62,6 +62,7 @@ BASE_EXPORT bool AdjustOOMScore(ProcessId process, int score); // IF YOU USE THIS WITHOUT CONSULTING YOUR FRIENDLY OSX DEVELOPER, // YOUR CODE IS LIKELY TO BE REVERTED. THANK YOU. BASE_EXPORT void* UncheckedMalloc(size_t size); +BASE_EXPORT void* UncheckedCalloc(size_t num_items, size_t size); #endif // defined(OS_MACOSX) } // namespace base diff --git a/chromium/base/process/memory_linux.cc b/chromium/base/process/memory_linux.cc index f81429b2ac0..6bed68bd832 100644 --- a/chromium/base/process/memory_linux.cc +++ b/chromium/base/process/memory_linux.cc @@ -18,6 +18,7 @@ size_t g_oom_size = 0U; namespace { +#if !defined(OS_ANDROID) void OnNoMemorySize(size_t size) { g_oom_size = size; @@ -29,6 +30,7 @@ void OnNoMemorySize(size_t size) { void OnNoMemory() { OnNoMemorySize(0); } +#endif // !defined(OS_ANDROID) } // namespace diff --git a/chromium/base/process/memory_mac.mm b/chromium/base/process/memory_mac.mm index dd30e704c8f..3e281cd8e3c 100644 --- a/chromium/base/process/memory_mac.mm +++ b/chromium/base/process/memory_mac.mm @@ -108,7 +108,7 @@ class ThreadLocalBooleanAutoReset { }; base::LazyInstance<ThreadLocalBoolean>::Leaky - g_unchecked_malloc = LAZY_INSTANCE_INITIALIZER; + g_unchecked_alloc = LAZY_INSTANCE_INITIALIZER; // NOTE(shess): This is called when the malloc library noticed that the heap // is fubar. Avoid calls which will re-enter the malloc library. @@ -117,10 +117,23 @@ void CrMallocErrorBreak() { // Out of memory is certainly not heap corruption, and not necessarily // something for which the process should be terminated. Leave that decision - // to the OOM killer. The EBADF case comes up because the malloc library - // attempts to log to ASL (syslog) before calling this code, which fails - // accessing a Unix-domain socket because of sandboxing. - if (errno == ENOMEM || (errno == EBADF && g_unchecked_malloc.Get().Get())) + // to the OOM killer. + if (errno == ENOMEM) + return; + + // The malloc library attempts to log to ASL (syslog) before calling this + // code, which fails accessing a Unix-domain socket when sandboxed. The + // failed socket results in writing to a -1 fd, leaving EBADF in errno. If + // UncheckedMalloc() is on the stack, for large allocations (15k and up) only + // an OOM failure leads here. Smaller allocations could also arrive here due + // to freelist corruption, but there is no way to distinguish that from OOM at + // this point. + // + // NOTE(shess): I hypothesize that EPERM case in 10.9 is the same root cause + // as EBADF. Unfortunately, 10.9's opensource releases don't include malloc + // source code at this time. + // <http://crbug.com/312234> + if ((errno == EBADF || errno == EPERM) && g_unchecked_alloc.Get().Get()) return; // A unit test checks this error message, so it needs to be in release builds. @@ -422,7 +435,7 @@ void oom_killer_new() { // === Core Foundation CFAllocators === bool CanGetContextForCFAllocator() { - return !base::mac::IsOSLaterThanMountainLion_DontCallThis(); + return !base::mac::IsOSLaterThanMavericks_DontCallThis(); } CFAllocatorContext* ContextForCFAllocator(CFAllocatorRef allocator) { @@ -431,7 +444,9 @@ CFAllocatorContext* ContextForCFAllocator(CFAllocatorRef allocator) { const_cast<ChromeCFAllocatorLeopards*>( reinterpret_cast<const ChromeCFAllocatorLeopards*>(allocator)); return &our_allocator->_context; - } else if (base::mac::IsOSLion() || base::mac::IsOSMountainLion()) { + } else if (base::mac::IsOSLion() || + base::mac::IsOSMountainLion() || + base::mac::IsOSMavericks()) { ChromeCFAllocatorLions* our_allocator = const_cast<ChromeCFAllocatorLions*>( reinterpret_cast<const ChromeCFAllocatorLions*>(allocator)); @@ -491,13 +506,24 @@ void* UncheckedMalloc(size_t size) { if (g_old_malloc) { #if ARCH_CPU_32_BITS ScopedClearErrno clear_errno; - ThreadLocalBooleanAutoReset flag(g_unchecked_malloc.Pointer(), true); + ThreadLocalBooleanAutoReset flag(g_unchecked_alloc.Pointer(), true); #endif // ARCH_CPU_32_BITS return g_old_malloc(malloc_default_zone(), size); } return malloc(size); } +void* UncheckedCalloc(size_t num_items, size_t size) { + if (g_old_calloc) { +#if ARCH_CPU_32_BITS + ScopedClearErrno clear_errno; + ThreadLocalBooleanAutoReset flag(g_unchecked_alloc.Pointer(), true); +#endif // ARCH_CPU_32_BITS + return g_old_calloc(malloc_default_zone(), num_items, size); + } + return calloc(num_items, size); +} + void EnableTerminationOnOutOfMemory() { if (g_oom_killer_enabled) return; diff --git a/chromium/base/process/memory_unittest.cc b/chromium/base/process/memory_unittest.cc index a1f30526aa3..e5c759d5711 100644 --- a/chromium/base/process/memory_unittest.cc +++ b/chromium/base/process/memory_unittest.cc @@ -10,6 +10,7 @@ #include "base/compiler_specific.h" #include "base/debug/alias.h" +#include "base/strings/stringprintf.h" #include "testing/gtest/include/gtest/gtest.h" #if defined(OS_WIN) @@ -23,7 +24,6 @@ #include "base/process/memory_unittest_mac.h" #endif #if defined(OS_LINUX) -#include <glib.h> #include <malloc.h> #endif @@ -260,14 +260,13 @@ TEST_F(OutOfMemoryDeathTest, Memalign) { } TEST_F(OutOfMemoryDeathTest, ViaSharedLibraries) { - // g_try_malloc is documented to return NULL on failure. (g_malloc is the - // 'safe' default that crashes if allocation fails). However, since we have - // hopefully overridden malloc, even g_try_malloc should fail. This tests - // that the run-time symbol resolution is overriding malloc for shared - // libraries as well as for our code. + // This tests that the run-time symbol resolution is overriding malloc for + // shared libraries (including libc itself) as well as for our code. + std::string format = base::StringPrintf("%%%zud", test_size_); + char *value = NULL; ASSERT_DEATH({ SetUpInDeathAssert(); - value_ = g_try_malloc(test_size_); + EXPECT_EQ(-1, asprintf(&value, format.c_str(), 0)); }, ""); } #endif // OS_LINUX diff --git a/chromium/base/process/process_handle_linux.cc b/chromium/base/process/process_handle_linux.cc index 91441f7b38c..0f7ccd348a8 100644 --- a/chromium/base/process/process_handle_linux.cc +++ b/chromium/base/process/process_handle_linux.cc @@ -20,7 +20,7 @@ ProcessId GetParentProcessId(ProcessHandle process) { FilePath GetProcessExecutablePath(ProcessHandle process) { FilePath stat_file = internal::GetProcPidDir(process).Append("exe"); FilePath exe_name; - if (!file_util::ReadSymbolicLink(stat_file, &exe_name)) { + if (!ReadSymbolicLink(stat_file, &exe_name)) { // No such process. Happens frequently in e.g. TerminateAllChromeProcesses return FilePath(); } diff --git a/chromium/base/process/process_metrics.cc b/chromium/base/process/process_metrics.cc index 127fb4649f7..83289b8c78b 100644 --- a/chromium/base/process/process_metrics.cc +++ b/chromium/base/process/process_metrics.cc @@ -42,4 +42,12 @@ scoped_ptr<Value> SystemMetrics::ToValue() const { return res.PassAs<Value>(); } +double ProcessMetrics::GetPlatformIndependentCPUUsage() { +#if defined(OS_WIN) + return GetCPUUsage() * processor_count_; +#else + return GetCPUUsage(); +#endif +} + } // namespace base diff --git a/chromium/base/process/process_metrics.h b/chromium/base/process/process_metrics.h index f6b225fa94f..560490a9b8c 100644 --- a/chromium/base/process/process_metrics.h +++ b/chromium/base/process/process_metrics.h @@ -160,14 +160,19 @@ class BASE_EXPORT ProcessMetrics { // load and fragmentation. bool CalculateFreeMemory(FreeMBytes* free) const; - // Returns the CPU usage in percent since the last time this method was - // called. The first time this method is called it returns 0 and will return - // the actual CPU info on subsequent calls. - // On Windows, the CPU usage value is for all CPUs. So if you have 2 CPUs and - // your process is using all the cycles of 1 CPU and not the other CPU, this - // method returns 50. + // Returns the CPU usage in percent since the last time this method or + // GetPlatformIndependentCPUUsage() was called. The first time this method + // is called it returns 0 and will return the actual CPU info on subsequent + // calls. On Windows, the CPU usage value is for all CPUs. So if you have + // 2 CPUs and your process is using all the cycles of 1 CPU and not the other + // CPU, this method returns 50. double GetCPUUsage(); + // Same as GetCPUUsage(), but will return consistent values on all platforms + // (cancelling the Windows exception mentioned above) by returning a value in + // the range of 0 to (100 * numCPUCores) everywhere. + double GetPlatformIndependentCPUUsage(); + // Retrieves accounting information for all I/O operations performed by the // process. // If IO information is retrieved successfully, the function returns true @@ -273,6 +278,16 @@ struct BASE_EXPORT SystemMemoryInfoKB { #endif }; +// Parses a string containing the contents of /proc/meminfo +// returns true on success or false for a parsing error +BASE_EXPORT bool ParseProcMeminfo(const std::string& input, + SystemMemoryInfoKB* meminfo); + +// Parses a string containing the contents of /proc/vmstat +// returns true on success or false for a parsing error +BASE_EXPORT bool ParseProcVmstat(const std::string& input, + SystemMemoryInfoKB* meminfo); + // Retrieves data from /proc/meminfo and /proc/vmstat // about system-wide memory consumption. // Fills in the provided |meminfo| structure. Returns true on success. diff --git a/chromium/base/process/process_metrics_linux.cc b/chromium/base/process/process_metrics_linux.cc index 7f46639e4a4..afa88486a0b 100644 --- a/chromium/base/process/process_metrics_linux.cc +++ b/chromium/base/process/process_metrics_linux.cc @@ -405,33 +405,6 @@ int GetNumberOfThreads(ProcessHandle process) { namespace { -// The format of /proc/meminfo is: -// -// MemTotal: 8235324 kB -// MemFree: 1628304 kB -// Buffers: 429596 kB -// Cached: 4728232 kB -// ... -const size_t kMemTotalIndex = 1; -const size_t kMemFreeIndex = 4; -const size_t kMemBuffersIndex = 7; -const size_t kMemCachedIndex = 10; -const size_t kMemActiveAnonIndex = 22; -const size_t kMemInactiveAnonIndex = 25; -const size_t kMemActiveFileIndex = 28; -const size_t kMemInactiveFileIndex = 31; - -// The format of /proc/vmstat is: -// -// nr_free_pages 299878 -// nr_inactive_anon 239863 -// nr_active_anon 1318966 -// nr_inactive_file 2015629 -// ... -const size_t kVMPagesSwappedIn = 75; -const size_t kVMPagesSwappedOut = 77; -const size_t kVMPageMajorFaults = 95; - // The format of /proc/diskstats is: // Device major number // Device minor number @@ -538,6 +511,123 @@ scoped_ptr<Value> SystemMemoryInfoKB::ToValue() const { return res.PassAs<Value>(); } +// exposed for testing +bool ParseProcMeminfo(const std::string& meminfo_data, + SystemMemoryInfoKB* meminfo) { + // The format of /proc/meminfo is: + // + // MemTotal: 8235324 kB + // MemFree: 1628304 kB + // Buffers: 429596 kB + // Cached: 4728232 kB + // ... + // There is no guarantee on the ordering or position + // though it doesn't appear to change very often + + // As a basic sanity check, let's make sure we at least get non-zero + // MemTotal value + meminfo->total = 0; + + std::vector<std::string> meminfo_lines; + Tokenize(meminfo_data, "\n", &meminfo_lines); + for (std::vector<std::string>::iterator it = meminfo_lines.begin(); + it != meminfo_lines.end(); ++it) { + std::vector<std::string> tokens; + SplitStringAlongWhitespace(*it, &tokens); + // HugePages_* only has a number and no suffix so we can't rely on + // there being exactly 3 tokens. + if (tokens.size() > 1) { + if (tokens[0] == "MemTotal:") { + StringToInt(tokens[1], &meminfo->total); + continue; + } if (tokens[0] == "MemFree:") { + StringToInt(tokens[1], &meminfo->free); + continue; + } if (tokens[0] == "Buffers:") { + StringToInt(tokens[1], &meminfo->buffers); + continue; + } if (tokens[0] == "Cached:") { + StringToInt(tokens[1], &meminfo->cached); + continue; + } if (tokens[0] == "Active(anon):") { + StringToInt(tokens[1], &meminfo->active_anon); + continue; + } if (tokens[0] == "Inactive(anon):") { + StringToInt(tokens[1], &meminfo->inactive_anon); + continue; + } if (tokens[0] == "Active(file):") { + StringToInt(tokens[1], &meminfo->active_file); + continue; + } if (tokens[0] == "Inactive(file):") { + StringToInt(tokens[1], &meminfo->inactive_file); + continue; + } if (tokens[0] == "SwapTotal:") { + StringToInt(tokens[1], &meminfo->swap_total); + continue; + } if (tokens[0] == "SwapFree:") { + StringToInt(tokens[1], &meminfo->swap_free); + continue; + } if (tokens[0] == "Dirty:") { + StringToInt(tokens[1], &meminfo->dirty); + continue; +#if defined(OS_CHROMEOS) + // Chrome OS has a tweaked kernel that allows us to query Shmem, which is + // usually video memory otherwise invisible to the OS. + } if (tokens[0] == "Shmem:") { + StringToInt(tokens[1], &meminfo->shmem); + continue; + } if (tokens[0] == "Slab:") { + StringToInt(tokens[1], &meminfo->slab); + continue; +#endif + } + } else + DLOG(WARNING) << "meminfo: tokens: " << tokens.size() + << " malformed line: " << *it; + } + + // Make sure we got a valid MemTotal. + if (!meminfo->total) + return false; + + return true; +} + +// exposed for testing +bool ParseProcVmstat(const std::string& vmstat_data, + SystemMemoryInfoKB* meminfo) { + // The format of /proc/vmstat is: + // + // nr_free_pages 299878 + // nr_inactive_anon 239863 + // nr_active_anon 1318966 + // nr_inactive_file 2015629 + // ... + // + // We iterate through the whole file because the position of the + // fields are dependent on the kernel version and configuration. + + std::vector<std::string> vmstat_lines; + Tokenize(vmstat_data, "\n", &vmstat_lines); + for (std::vector<std::string>::iterator it = vmstat_lines.begin(); + it != vmstat_lines.end(); ++it) { + std::vector<std::string> tokens; + SplitString(*it, ' ', &tokens); + if (tokens.size() == 2) { + if (tokens[0] == "pswpin") { + StringToInt(tokens[1], &meminfo->pswpin); + continue; + } if (tokens[0] == "pswpout") { + StringToInt(tokens[1], &meminfo->pswpout); + continue; + } if (tokens[0] == "pgmajfault") + StringToInt(tokens[1], &meminfo->pgmajfault); + } + } + + return true; +} + bool GetSystemMemoryInfo(SystemMemoryInfoKB* meminfo) { // Synchronously reading files in /proc is safe. ThreadRestrictions::ScopedAllowIO allow_io; @@ -549,55 +639,13 @@ bool GetSystemMemoryInfo(SystemMemoryInfoKB* meminfo) { DLOG(WARNING) << "Failed to open " << meminfo_file.value(); return false; } - std::vector<std::string> meminfo_fields; - SplitStringAlongWhitespace(meminfo_data, &meminfo_fields); - if (meminfo_fields.size() < kMemCachedIndex) { - DLOG(WARNING) << "Failed to parse " << meminfo_file.value() - << ". Only found " << meminfo_fields.size() << " fields."; + if (!ParseProcMeminfo(meminfo_data, meminfo)) { + DLOG(WARNING) << "Failed to parse " << meminfo_file.value(); return false; } - DCHECK_EQ(meminfo_fields[kMemTotalIndex-1], "MemTotal:"); - DCHECK_EQ(meminfo_fields[kMemFreeIndex-1], "MemFree:"); - DCHECK_EQ(meminfo_fields[kMemBuffersIndex-1], "Buffers:"); - DCHECK_EQ(meminfo_fields[kMemCachedIndex-1], "Cached:"); - DCHECK_EQ(meminfo_fields[kMemActiveAnonIndex-1], "Active(anon):"); - DCHECK_EQ(meminfo_fields[kMemInactiveAnonIndex-1], "Inactive(anon):"); - DCHECK_EQ(meminfo_fields[kMemActiveFileIndex-1], "Active(file):"); - DCHECK_EQ(meminfo_fields[kMemInactiveFileIndex-1], "Inactive(file):"); - - StringToInt(meminfo_fields[kMemTotalIndex], &meminfo->total); - StringToInt(meminfo_fields[kMemFreeIndex], &meminfo->free); - StringToInt(meminfo_fields[kMemBuffersIndex], &meminfo->buffers); - StringToInt(meminfo_fields[kMemCachedIndex], &meminfo->cached); - StringToInt(meminfo_fields[kMemActiveAnonIndex], &meminfo->active_anon); - StringToInt(meminfo_fields[kMemInactiveAnonIndex], &meminfo->inactive_anon); - StringToInt(meminfo_fields[kMemActiveFileIndex], &meminfo->active_file); - StringToInt(meminfo_fields[kMemInactiveFileIndex], &meminfo->inactive_file); - - // We don't know when these fields appear, so we must search for them. - for (size_t i = kMemCachedIndex+2; i < meminfo_fields.size(); i += 3) { - if (meminfo_fields[i] == "SwapTotal:") - StringToInt(meminfo_fields[i+1], &meminfo->swap_total); - if (meminfo_fields[i] == "SwapFree:") - StringToInt(meminfo_fields[i+1], &meminfo->swap_free); - if (meminfo_fields[i] == "Dirty:") - StringToInt(meminfo_fields[i+1], &meminfo->dirty); - } - #if defined(OS_CHROMEOS) - // Chrome OS has a tweaked kernel that allows us to query Shmem, which is - // usually video memory otherwise invisible to the OS. Unfortunately, the - // meminfo format varies on different hardware so we have to search for the - // string. It always appears after "Cached:". - for (size_t i = kMemCachedIndex+2; i < meminfo_fields.size(); i += 3) { - if (meminfo_fields[i] == "Shmem:") - StringToInt(meminfo_fields[i+1], &meminfo->shmem); - if (meminfo_fields[i] == "Slab:") - StringToInt(meminfo_fields[i+1], &meminfo->slab); - } - // Report on Chrome OS GEM object graphics memory. /var/run/debugfs_gpu is a // bind mount into /sys/kernel/debug and synchronously reading the in-memory // files in /sys is fast. @@ -640,15 +688,10 @@ bool GetSystemMemoryInfo(SystemMemoryInfoKB* meminfo) { DLOG(WARNING) << "Failed to open " << vmstat_file.value(); return false; } - - std::vector<std::string> vmstat_fields; - SplitStringAlongWhitespace(vmstat_data, &vmstat_fields); - if (vmstat_fields[kVMPagesSwappedIn-1] == "pswpin") - StringToInt(vmstat_fields[kVMPagesSwappedIn], &meminfo->pswpin); - if (vmstat_fields[kVMPagesSwappedOut-1] == "pswpout") - StringToInt(vmstat_fields[kVMPagesSwappedOut], &meminfo->pswpout); - if (vmstat_fields[kVMPageMajorFaults-1] == "pgmajfault") - StringToInt(vmstat_fields[kVMPageMajorFaults], &meminfo->pgmajfault); + if (!ParseProcVmstat(vmstat_data, meminfo)) { + DLOG(WARNING) << "Failed to parse " << vmstat_file.value(); + return false; + } return true; } diff --git a/chromium/base/process/process_metrics_unittest.cc b/chromium/base/process/process_metrics_unittest.cc new file mode 100644 index 00000000000..5d365d9a203 --- /dev/null +++ b/chromium/base/process/process_metrics_unittest.cc @@ -0,0 +1,397 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/process_metrics.h" + +#include <sstream> +#include <string> + +#include "base/threading/thread.h" +#include "testing/gtest/include/gtest/gtest.h" + + +namespace base { +namespace debug { + +// Tests for SystemMetrics. +// Exists as a class so it can be a friend of SystemMetrics. +class SystemMetricsTest : public testing::Test { + public: + SystemMetricsTest() {} + + private: + DISALLOW_COPY_AND_ASSIGN(SystemMetricsTest); +}; + +///////////////////////////////////////////////////////////////////////////// + +#if defined(OS_LINUX) || defined(OS_ANDROID) +TEST_F(SystemMetricsTest, IsValidDiskName) { + std::string invalid_input1 = ""; + std::string invalid_input2 = "s"; + std::string invalid_input3 = "sdz+"; + std::string invalid_input4 = "hda0"; + std::string invalid_input5 = "mmcbl"; + std::string invalid_input6 = "mmcblka"; + std::string invalid_input7 = "mmcblkb"; + std::string invalid_input8 = "mmmblk0"; + + EXPECT_FALSE(IsValidDiskName(invalid_input1)); + EXPECT_FALSE(IsValidDiskName(invalid_input2)); + EXPECT_FALSE(IsValidDiskName(invalid_input3)); + EXPECT_FALSE(IsValidDiskName(invalid_input4)); + EXPECT_FALSE(IsValidDiskName(invalid_input5)); + EXPECT_FALSE(IsValidDiskName(invalid_input6)); + EXPECT_FALSE(IsValidDiskName(invalid_input7)); + EXPECT_FALSE(IsValidDiskName(invalid_input8)); + + std::string valid_input1 = "sda"; + std::string valid_input2 = "sdaaaa"; + std::string valid_input3 = "hdz"; + std::string valid_input4 = "mmcblk0"; + std::string valid_input5 = "mmcblk999"; + + EXPECT_TRUE(IsValidDiskName(valid_input1)); + EXPECT_TRUE(IsValidDiskName(valid_input2)); + EXPECT_TRUE(IsValidDiskName(valid_input3)); + EXPECT_TRUE(IsValidDiskName(valid_input4)); + EXPECT_TRUE(IsValidDiskName(valid_input5)); +} + +TEST_F(SystemMetricsTest, ParseMeminfo) { + struct SystemMemoryInfoKB meminfo; + std::string invalid_input1 = "abc"; + std::string invalid_input2 = "MemTotal:"; + // Partial file with no MemTotal + std::string invalid_input3 = + "MemFree: 3913968 kB\n" + "Buffers: 2348340 kB\n" + "Cached: 49071596 kB\n" + "SwapCached: 12 kB\n" + "Active: 36393900 kB\n" + "Inactive: 21221496 kB\n" + "Active(anon): 5674352 kB\n" + "Inactive(anon): 633992 kB\n"; + EXPECT_FALSE(ParseProcMeminfo(invalid_input1, &meminfo)); + EXPECT_FALSE(ParseProcMeminfo(invalid_input2, &meminfo)); + EXPECT_FALSE(ParseProcMeminfo(invalid_input3, &meminfo)); + + std::string valid_input1 = + "MemTotal: 3981504 kB\n" + "MemFree: 140764 kB\n" + "Buffers: 116480 kB\n" + "Cached: 406160 kB\n" + "SwapCached: 21304 kB\n" + "Active: 3152040 kB\n" + "Inactive: 472856 kB\n" + "Active(anon): 2972352 kB\n" + "Inactive(anon): 270108 kB\n" + "Active(file): 179688 kB\n" + "Inactive(file): 202748 kB\n" + "Unevictable: 0 kB\n" + "Mlocked: 0 kB\n" + "SwapTotal: 5832280 kB\n" + "SwapFree: 3672368 kB\n" + "Dirty: 184 kB\n" + "Writeback: 0 kB\n" + "AnonPages: 3101224 kB\n" + "Mapped: 142296 kB\n" + "Shmem: 140204 kB\n" + "Slab: 54212 kB\n" + "SReclaimable: 30936 kB\n" + "SUnreclaim: 23276 kB\n" + "KernelStack: 2464 kB\n" + "PageTables: 24812 kB\n" + "NFS_Unstable: 0 kB\n" + "Bounce: 0 kB\n" + "WritebackTmp: 0 kB\n" + "CommitLimit: 7823032 kB\n" + "Committed_AS: 7973536 kB\n" + "VmallocTotal: 34359738367 kB\n" + "VmallocUsed: 375940 kB\n" + "VmallocChunk: 34359361127 kB\n" + "DirectMap4k: 72448 kB\n" + "DirectMap2M: 4061184 kB\n"; + // output from a much older kernel where the Active and Inactive aren't + // broken down into anon and file and Huge Pages are enabled + std::string valid_input2 = + "MemTotal: 255908 kB\n" + "MemFree: 69936 kB\n" + "Buffers: 15812 kB\n" + "Cached: 115124 kB\n" + "SwapCached: 0 kB\n" + "Active: 92700 kB\n" + "Inactive: 63792 kB\n" + "HighTotal: 0 kB\n" + "HighFree: 0 kB\n" + "LowTotal: 255908 kB\n" + "LowFree: 69936 kB\n" + "SwapTotal: 524280 kB\n" + "SwapFree: 524200 kB\n" + "Dirty: 4 kB\n" + "Writeback: 0 kB\n" + "Mapped: 42236 kB\n" + "Slab: 25912 kB\n" + "Committed_AS: 118680 kB\n" + "PageTables: 1236 kB\n" + "VmallocTotal: 3874808 kB\n" + "VmallocUsed: 1416 kB\n" + "VmallocChunk: 3872908 kB\n" + "HugePages_Total: 0\n" + "HugePages_Free: 0\n" + "Hugepagesize: 4096 kB\n"; + + EXPECT_TRUE(ParseProcMeminfo(valid_input1, &meminfo)); + EXPECT_TRUE(meminfo.total == 3981504); + EXPECT_TRUE(meminfo.free == 140764); + EXPECT_TRUE(meminfo.buffers == 116480); + EXPECT_TRUE(meminfo.cached == 406160); + EXPECT_TRUE(meminfo.active_anon == 2972352); + EXPECT_TRUE(meminfo.active_file == 179688); + EXPECT_TRUE(meminfo.inactive_anon == 270108); + EXPECT_TRUE(meminfo.inactive_file == 202748); + EXPECT_TRUE(meminfo.swap_total == 5832280); + EXPECT_TRUE(meminfo.swap_free == 3672368); + EXPECT_TRUE(meminfo.dirty == 184); +#if defined(OS_CHROMEOS) + EXPECT_TRUE(meminfo.shmem == 140204); + EXPECT_TRUE(meminfo.slab == 54212); +#endif + EXPECT_TRUE(ParseProcMeminfo(valid_input2, &meminfo)); + EXPECT_TRUE(meminfo.total == 255908); + EXPECT_TRUE(meminfo.free == 69936); + EXPECT_TRUE(meminfo.buffers == 15812); + EXPECT_TRUE(meminfo.cached == 115124); + EXPECT_TRUE(meminfo.swap_total == 524280); + EXPECT_TRUE(meminfo.swap_free == 524200); + EXPECT_TRUE(meminfo.dirty == 4); +} + +TEST_F(SystemMetricsTest, ParseVmstat) { + struct SystemMemoryInfoKB meminfo; + // part of vmstat from a 3.2 kernel with numa enabled + std::string valid_input1 = + "nr_free_pages 905104\n" + "nr_inactive_anon 142478" + "nr_active_anon 1520046\n" + "nr_inactive_file 4481001\n" + "nr_active_file 8313439\n" + "nr_unevictable 5044\n" + "nr_mlock 5044\n" + "nr_anon_pages 1633780\n" + "nr_mapped 104742\n" + "nr_file_pages 12828218\n" + "nr_dirty 245\n" + "nr_writeback 0\n" + "nr_slab_reclaimable 831609\n" + "nr_slab_unreclaimable 41164\n" + "nr_page_table_pages 31470\n" + "nr_kernel_stack 1735\n" + "nr_unstable 0\n" + "nr_bounce 0\n" + "nr_vmscan_write 406\n" + "nr_vmscan_immediate_reclaim 281\n" + "nr_writeback_temp 0\n" + "nr_isolated_anon 0\n" + "nr_isolated_file 0\n" + "nr_shmem 28820\n" + "nr_dirtied 84674644\n" + "nr_written 75307109\n" + "nr_anon_transparent_hugepages 0\n" + "nr_dirty_threshold 1536206\n" + "nr_dirty_background_threshold 768103\n" + "pgpgin 30777108\n" + "pgpgout 319023278\n" + "pswpin 179\n" + "pswpout 406\n" + "pgalloc_dma 0\n" + "pgalloc_dma32 20833399\n" + "pgalloc_normal 1622609290\n" + "pgalloc_movable 0\n" + "pgfree 1644355583\n" + "pgactivate 75391882\n" + "pgdeactivate 4121019\n" + "pgfault 2542879679\n" + "pgmajfault 487192\n"; + std::string valid_input2 = + "nr_free_pages 180125\n" + "nr_inactive_anon 51\n" + "nr_active_anon 38832\n" + "nr_inactive_file 50171\n" + "nr_active_file 47510\n" + "nr_unevictable 0\n" + "nr_mlock 0\n" + "nr_anon_pages 38825\n" + "nr_mapped 24043\n" + "nr_file_pages 97733\n" + "nr_dirty 0\n" + "nr_writeback 0\n" + "nr_slab_reclaimable 4032\n" + "nr_slab_unreclaimable 2848\n" + "nr_page_table_pages 1505\n" + "nr_kernel_stack 626\n" + "nr_unstable 0\n" + "nr_bounce 0\n" + "nr_vmscan_write 0\n" + "nr_vmscan_immediate_reclaim 0\n" + "nr_writeback_temp 0\n" + "nr_isolated_anon 0\n" + "nr_isolated_file 0\n" + "nr_shmem 58\n" + "nr_dirtied 435358\n" + "nr_written 401258\n" + "nr_anon_transparent_hugepages 0\n" + "nr_dirty_threshold 18566\n" + "nr_dirty_background_threshold 4641\n" + "pgpgin 299464\n" + "pgpgout 2437788\n" + "pswpin 12\n" + "pswpout 901\n" + "pgalloc_normal 144213030\n" + "pgalloc_high 164501274\n" + "pgalloc_movable 0\n" + "pgfree 308894908\n" + "pgactivate 239320\n" + "pgdeactivate 1\n" + "pgfault 716044601\n" + "pgmajfault 2023\n" + "pgrefill_normal 0\n" + "pgrefill_high 0\n" + "pgrefill_movable 0\n"; + EXPECT_TRUE(ParseProcVmstat(valid_input1, &meminfo)); + EXPECT_TRUE(meminfo.pswpin == 179); + EXPECT_TRUE(meminfo.pswpout == 406); + EXPECT_TRUE(meminfo.pgmajfault == 487192); + EXPECT_TRUE(ParseProcVmstat(valid_input2, &meminfo)); + EXPECT_TRUE(meminfo.pswpin == 12); + EXPECT_TRUE(meminfo.pswpout == 901); + EXPECT_TRUE(meminfo.pgmajfault == 2023); +} +#endif // defined(OS_LINUX) || defined(OS_ANDROID) + +#if defined(OS_LINUX) || defined(OS_ANDROID) +TEST(SystemMetrics2Test, GetSystemMemoryInfo) { + base::SystemMemoryInfoKB info; + EXPECT_TRUE(base::GetSystemMemoryInfo(&info)); + + // Ensure each field received a value. + EXPECT_GT(info.total, 0); + EXPECT_GT(info.free, 0); + EXPECT_GT(info.buffers, 0); + EXPECT_GT(info.cached, 0); + EXPECT_GT(info.active_anon, 0); + EXPECT_GT(info.inactive_anon, 0); + EXPECT_GT(info.active_file, 0); + EXPECT_GT(info.inactive_file, 0); + + // All the values should be less than the total amount of memory. + EXPECT_LT(info.free, info.total); + EXPECT_LT(info.buffers, info.total); + EXPECT_LT(info.cached, info.total); + EXPECT_LT(info.active_anon, info.total); + EXPECT_LT(info.inactive_anon, info.total); + EXPECT_LT(info.active_file, info.total); + EXPECT_LT(info.inactive_file, info.total); + +#if defined(OS_CHROMEOS) + // Chrome OS exposes shmem. + EXPECT_GT(info.shmem, 0); + EXPECT_LT(info.shmem, info.total); + // Chrome unit tests are not run on actual Chrome OS hardware, so gem_objects + // and gem_size cannot be tested here. +#endif +} +#endif // defined(OS_LINUX) || defined(OS_ANDROID) + +#if defined(OS_WIN) +// TODO(estade): if possible, port this test. +TEST(ProcessMetricsTest, CalcFreeMemory) { + scoped_ptr<base::ProcessMetrics> metrics( + base::ProcessMetrics::CreateProcessMetrics(::GetCurrentProcess())); + ASSERT_TRUE(NULL != metrics.get()); + + bool using_tcmalloc = false; + + // Detect if we are using tcmalloc +#if !defined(NO_TCMALLOC) + const char* chrome_allocator = getenv("CHROME_ALLOCATOR"); + if (!chrome_allocator || _stricmp(chrome_allocator, "tcmalloc") == 0) + using_tcmalloc = true; +#endif + + // Typical values here is ~1900 for total and ~1000 for largest. Obviously + // it depends in what other tests have done to this process. + base::FreeMBytes free_mem1 = {0}; + EXPECT_TRUE(metrics->CalculateFreeMemory(&free_mem1)); + EXPECT_LT(10u, free_mem1.total); + EXPECT_LT(10u, free_mem1.largest); + EXPECT_GT(2048u, free_mem1.total); + EXPECT_GT(2048u, free_mem1.largest); + EXPECT_GE(free_mem1.total, free_mem1.largest); + EXPECT_TRUE(NULL != free_mem1.largest_ptr); + + // Allocate 20M and check again. It should have gone down. + const int kAllocMB = 20; + scoped_ptr<char[]> alloc(new char[kAllocMB * 1024 * 1024]); + size_t expected_total = free_mem1.total - kAllocMB; + size_t expected_largest = free_mem1.largest; + + base::FreeMBytes free_mem2 = {0}; + EXPECT_TRUE(metrics->CalculateFreeMemory(&free_mem2)); + EXPECT_GE(free_mem2.total, free_mem2.largest); + // This test is flaky when using tcmalloc, because tcmalloc + // allocation strategy sometimes results in less than the + // full drop of 20Mb of free memory. + if (!using_tcmalloc) + EXPECT_GE(expected_total, free_mem2.total); + EXPECT_GE(expected_largest, free_mem2.largest); + EXPECT_TRUE(NULL != free_mem2.largest_ptr); +} +#endif // defined(OS_WIN) + +#if defined(OS_LINUX) || defined(OS_ANDROID) +TEST(ProcessMetricsTest, ParseProcStatCPU) { + // /proc/self/stat for a process running "top". + const char kTopStat[] = "960 (top) S 16230 960 16230 34818 960 " + "4202496 471 0 0 0 " + "12 16 0 0 " // <- These are the goods. + "20 0 1 0 121946157 15077376 314 18446744073709551615 4194304 " + "4246868 140733983044336 18446744073709551615 140244213071219 " + "0 0 0 138047495 0 0 0 17 1 0 0 0 0 0"; + EXPECT_EQ(12 + 16, base::ParseProcStatCPU(kTopStat)); + + // cat /proc/self/stat on a random other machine I have. + const char kSelfStat[] = "5364 (cat) R 5354 5364 5354 34819 5364 " + "0 142 0 0 0 " + "0 0 0 0 " // <- No CPU, apparently. + "16 0 1 0 1676099790 2957312 114 4294967295 134512640 134528148 " + "3221224832 3221224344 3086339742 0 0 0 0 0 0 0 17 0 0 0"; + + EXPECT_EQ(0, base::ParseProcStatCPU(kSelfStat)); +} +#endif // defined(OS_LINUX) || defined(OS_ANDROID) + +// Disable on Android because base_unittests runs inside a Dalvik VM that +// starts and stop threads (crbug.com/175563). +#if defined(OS_LINUX) +TEST(ProcessMetricsTest, GetNumberOfThreads) { + const base::ProcessHandle current = base::GetCurrentProcessHandle(); + const int initial_threads = base::GetNumberOfThreads(current); + ASSERT_GT(initial_threads, 0); + const int kNumAdditionalThreads = 10; + { + scoped_ptr<base::Thread> my_threads[kNumAdditionalThreads]; + for (int i = 0; i < kNumAdditionalThreads; ++i) { + my_threads[i].reset(new base::Thread("GetNumberOfThreadsTest")); + my_threads[i]->Start(); + ASSERT_EQ(base::GetNumberOfThreads(current), initial_threads + 1 + i); + } + } + // The Thread destructor will stop them. + ASSERT_EQ(initial_threads, base::GetNumberOfThreads(current)); +} +#endif // defined(OS_LINUX) + +} // namespace debug +} // namespace base diff --git a/chromium/base/process/process_util_unittest_ios.cc b/chromium/base/process/process_metrics_unittest_ios.cc index cad0f1b09f0..3e1ca35b18c 100644 --- a/chromium/base/process/process_util_unittest_ios.cc +++ b/chromium/base/process/process_metrics_unittest_ios.cc @@ -1,12 +1,13 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "base/memory/scoped_ptr.h" #include "base/process/process_metrics.h" + +#include "base/memory/scoped_ptr.h" #include "testing/gtest/include/gtest/gtest.h" -TEST(ProcessUtilTestIos, Memory) { +TEST(ProcessMetricsTestIos, Memory) { scoped_ptr<base::ProcessMetrics> process_metrics( base::ProcessMetrics::CreateProcessMetrics( base::GetCurrentProcessHandle())); diff --git a/chromium/base/process/process_metrics_unittests.cc b/chromium/base/process/process_metrics_unittests.cc deleted file mode 100644 index 387d860eda5..00000000000 --- a/chromium/base/process/process_metrics_unittests.cc +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "base/process/process_metrics.h" - -#include <sstream> -#include <string> - -#include "testing/gtest/include/gtest/gtest.h" - - -namespace base { -namespace debug { - -// Tests for SystemMetrics. -// Exists as a class so it can be a friend of SystemMetrics. -class SystemMetricsTest : public testing::Test { - public: - SystemMetricsTest() {} - - private: - DISALLOW_COPY_AND_ASSIGN(SystemMetricsTest); -}; - -///////////////////////////////////////////////////////////////////////////// - -#if defined(OS_LINUX) || defined(OS_ANDROID) -TEST_F(SystemMetricsTest, IsValidDiskName) { - std::string invalid_input1 = ""; - std::string invalid_input2 = "s"; - std::string invalid_input3 = "sdz+"; - std::string invalid_input4 = "hda0"; - std::string invalid_input5 = "mmcbl"; - std::string invalid_input6 = "mmcblka"; - std::string invalid_input7 = "mmcblkb"; - std::string invalid_input8 = "mmmblk0"; - - EXPECT_FALSE(IsValidDiskName(invalid_input1)); - EXPECT_FALSE(IsValidDiskName(invalid_input2)); - EXPECT_FALSE(IsValidDiskName(invalid_input3)); - EXPECT_FALSE(IsValidDiskName(invalid_input4)); - EXPECT_FALSE(IsValidDiskName(invalid_input5)); - EXPECT_FALSE(IsValidDiskName(invalid_input6)); - EXPECT_FALSE(IsValidDiskName(invalid_input7)); - EXPECT_FALSE(IsValidDiskName(invalid_input8)); - - std::string valid_input1 = "sda"; - std::string valid_input2 = "sdaaaa"; - std::string valid_input3 = "hdz"; - std::string valid_input4 = "mmcblk0"; - std::string valid_input5 = "mmcblk999"; - - EXPECT_TRUE(IsValidDiskName(valid_input1)); - EXPECT_TRUE(IsValidDiskName(valid_input2)); - EXPECT_TRUE(IsValidDiskName(valid_input3)); - EXPECT_TRUE(IsValidDiskName(valid_input4)); - EXPECT_TRUE(IsValidDiskName(valid_input5)); -} -#endif // defined(OS_LINUX) || defined(OS_ANDROID) - -} // namespace debug -} // namespace base diff --git a/chromium/base/process/process_util_unittest.cc b/chromium/base/process/process_util_unittest.cc index 44be9f4a273..6bfc1d07388 100644 --- a/chromium/base/process/process_util_unittest.cc +++ b/chromium/base/process/process_util_unittest.cc @@ -19,7 +19,9 @@ #include "base/process/memory.h" #include "base/process/process.h" #include "base/process/process_metrics.h" +#include "base/strings/string_number_conversions.h" #include "base/strings/utf_string_conversions.h" +#include "base/synchronization/waitable_event.h" #include "base/test/multiprocess_test.h" #include "base/test/test_timeouts.h" #include "base/third_party/dynamic_annotations/dynamic_annotations.h" @@ -29,7 +31,6 @@ #include "testing/multiprocess_func_list.h" #if defined(OS_LINUX) -#include <glib.h> #include <malloc.h> #include <sched.h> #endif @@ -44,6 +45,7 @@ #endif #if defined(OS_WIN) #include <windows.h> +#include "base/win/windows_version.h" #endif #if defined(OS_MACOSX) #include <mach/vm_param.h> @@ -54,12 +56,6 @@ using base::FilePath; namespace { -#if defined(OS_WIN) -const wchar_t kProcessName[] = L"base_unittests.exe"; -#else -const wchar_t kProcessName[] = L"base_unittests"; -#endif // defined(OS_WIN) - #if defined(OS_ANDROID) const char kShellPath[] = "/system/bin/sh"; const char kPosixShell[] = "sh"; @@ -69,7 +65,6 @@ const char kPosixShell[] = "bash"; #endif const char kSignalFileSlow[] = "SlowChildProcess.die"; -const char kSignalFileCrash[] = "CrashingChildProcess.die"; const char kSignalFileKill[] = "KilledChildProcess.die"; #if defined(OS_WIN) @@ -221,6 +216,7 @@ TEST_F(ProcessUtilTest, GetProcId) { // TODO(gspencer): turn this test process into a very small program // with no symbols (instead of using the multiprocess testing // framework) to reduce the ReportCrash overhead. +const char kSignalFileCrash[] = "CrashingChildProcess.die"; MULTIPROCESS_TEST_MAIN(CrashingChildProcess) { WaitToDie(ProcessUtilTest::GetSignalFilePath(kSignalFileCrash).c_str()); @@ -360,85 +356,8 @@ TEST_F(ProcessUtilTest, SetProcessBackgroundedSelf) { EXPECT_EQ(old_priority, new_priority); } -#if defined(OS_LINUX) || defined(OS_ANDROID) -TEST_F(ProcessUtilTest, GetSystemMemoryInfo) { - base::SystemMemoryInfoKB info; - EXPECT_TRUE(base::GetSystemMemoryInfo(&info)); - - // Ensure each field received a value. - EXPECT_GT(info.total, 0); - EXPECT_GT(info.free, 0); - EXPECT_GT(info.buffers, 0); - EXPECT_GT(info.cached, 0); - EXPECT_GT(info.active_anon, 0); - EXPECT_GT(info.inactive_anon, 0); - EXPECT_GT(info.active_file, 0); - EXPECT_GT(info.inactive_file, 0); - - // All the values should be less than the total amount of memory. - EXPECT_LT(info.free, info.total); - EXPECT_LT(info.buffers, info.total); - EXPECT_LT(info.cached, info.total); - EXPECT_LT(info.active_anon, info.total); - EXPECT_LT(info.inactive_anon, info.total); - EXPECT_LT(info.active_file, info.total); - EXPECT_LT(info.inactive_file, info.total); - -#if defined(OS_CHROMEOS) - // Chrome OS exposes shmem. - EXPECT_GT(info.shmem, 0); - EXPECT_LT(info.shmem, info.total); - // Chrome unit tests are not run on actual Chrome OS hardware, so gem_objects - // and gem_size cannot be tested here. -#endif -} -#endif // defined(OS_LINUX) || defined(OS_ANDROID) - -// TODO(estade): if possible, port these 2 tests. #if defined(OS_WIN) -TEST_F(ProcessUtilTest, CalcFreeMemory) { - scoped_ptr<base::ProcessMetrics> metrics( - base::ProcessMetrics::CreateProcessMetrics(::GetCurrentProcess())); - ASSERT_TRUE(NULL != metrics.get()); - - bool using_tcmalloc = false; - - // Detect if we are using tcmalloc -#if !defined(NO_TCMALLOC) - const char* chrome_allocator = getenv("CHROME_ALLOCATOR"); - if (!chrome_allocator || _stricmp(chrome_allocator, "tcmalloc") == 0) - using_tcmalloc = true; -#endif - - // Typical values here is ~1900 for total and ~1000 for largest. Obviously - // it depends in what other tests have done to this process. - base::FreeMBytes free_mem1 = {0}; - EXPECT_TRUE(metrics->CalculateFreeMemory(&free_mem1)); - EXPECT_LT(10u, free_mem1.total); - EXPECT_LT(10u, free_mem1.largest); - EXPECT_GT(2048u, free_mem1.total); - EXPECT_GT(2048u, free_mem1.largest); - EXPECT_GE(free_mem1.total, free_mem1.largest); - EXPECT_TRUE(NULL != free_mem1.largest_ptr); - - // Allocate 20M and check again. It should have gone down. - const int kAllocMB = 20; - scoped_ptr<char[]> alloc(new char[kAllocMB * 1024 * 1024]); - size_t expected_total = free_mem1.total - kAllocMB; - size_t expected_largest = free_mem1.largest; - - base::FreeMBytes free_mem2 = {0}; - EXPECT_TRUE(metrics->CalculateFreeMemory(&free_mem2)); - EXPECT_GE(free_mem2.total, free_mem2.largest); - // This test is flaky when using tcmalloc, because tcmalloc - // allocation strategy sometimes results in less than the - // full drop of 20Mb of free memory. - if (!using_tcmalloc) - EXPECT_GE(expected_total, free_mem2.total); - EXPECT_GE(expected_largest, free_mem2.largest); - EXPECT_TRUE(NULL != free_mem2.largest_ptr); -} - +// TODO(estade): if possible, port this test. TEST_F(ProcessUtilTest, GetAppOutput) { // Let's create a decently long message. std::string message; @@ -468,16 +387,64 @@ TEST_F(ProcessUtilTest, GetAppOutput) { EXPECT_EQ("", output); } +// TODO(estade): if possible, port this test. TEST_F(ProcessUtilTest, LaunchAsUser) { base::UserTokenHandle token; ASSERT_TRUE(OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &token)); - std::wstring cmdline = - this->MakeCmdLine("SimpleChildProcess", false).GetCommandLineString(); base::LaunchOptions options; options.as_user = token; - EXPECT_TRUE(base::LaunchProcess(cmdline, options, NULL)); + EXPECT_TRUE(base::LaunchProcess( + this->MakeCmdLine("SimpleChildProcess", false), options, NULL)); +} + +static const char kEventToTriggerHandleSwitch[] = "event-to-trigger-handle"; + +MULTIPROCESS_TEST_MAIN(TriggerEventChildProcess) { + std::string handle_value_string = + CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + kEventToTriggerHandleSwitch); + CHECK(!handle_value_string.empty()); + + uint64 handle_value_uint64; + CHECK(base::StringToUint64(handle_value_string, &handle_value_uint64)); + // Give ownership of the handle to |event|. + base::WaitableEvent event(reinterpret_cast<HANDLE>(handle_value_uint64)); + + event.Signal(); + + return 0; } +TEST_F(ProcessUtilTest, InheritSpecifiedHandles) { + // Manually create the event, so that it can be inheritable. + SECURITY_ATTRIBUTES security_attributes = {}; + security_attributes.nLength = static_cast<DWORD>(sizeof(security_attributes)); + security_attributes.lpSecurityDescriptor = NULL; + security_attributes.bInheritHandle = true; + + // Takes ownership of the event handle. + base::WaitableEvent event( + CreateEvent(&security_attributes, true, false, NULL)); + base::HandlesToInheritVector handles_to_inherit; + handles_to_inherit.push_back(event.handle()); + base::LaunchOptions options; + options.handles_to_inherit = &handles_to_inherit; + + CommandLine cmd_line = MakeCmdLine("TriggerEventChildProcess", false); + cmd_line.AppendSwitchASCII(kEventToTriggerHandleSwitch, + base::Uint64ToString(reinterpret_cast<uint64>(event.handle()))); + + // This functionality actually requires Vista or later. Make sure that it + // fails properly on XP. + if (base::win::GetVersion() < base::win::VERSION_VISTA) { + EXPECT_FALSE(base::LaunchProcess(cmd_line, options, NULL)); + return; + } + + // Launch the process and wait for it to trigger the event. + ASSERT_TRUE(base::LaunchProcess(cmd_line, options, NULL)); + EXPECT_TRUE(event.TimedWait(TestTimeouts::action_max_timeout())); +} #endif // defined(OS_WIN) #if defined(OS_POSIX) @@ -525,7 +492,7 @@ MULTIPROCESS_TEST_MAIN(ProcessUtilsLeakFDChildProcess) { int written = HANDLE_EINTR(write(write_pipe, &num_open_files, sizeof(num_open_files))); DCHECK_EQ(static_cast<size_t>(written), sizeof(num_open_files)); - int ret = HANDLE_EINTR(close(write_pipe)); + int ret = IGNORE_EINTR(close(write_pipe)); DPCHECK(ret == 0); return 0; @@ -541,7 +508,7 @@ int ProcessUtilTest::CountOpenFDsInChild() { base::ProcessHandle handle = this->SpawnChild( "ProcessUtilsLeakFDChildProcess", fd_mapping_vec, false); CHECK(handle); - int ret = HANDLE_EINTR(close(fds[1])); + int ret = IGNORE_EINTR(close(fds[1])); DPCHECK(ret == 0); // Read number of open files in client process from pipe; @@ -557,7 +524,7 @@ int ProcessUtilTest::CountOpenFDsInChild() { CHECK(base::WaitForSingleProcess(handle, base::TimeDelta::FromSeconds(1))); #endif base::CloseProcessHandle(handle); - ret = HANDLE_EINTR(close(fds[0])); + ret = IGNORE_EINTR(close(fds[0])); DPCHECK(ret == 0); return num_open_files; @@ -585,11 +552,11 @@ TEST_F(ProcessUtilTest, MAYBE_FDRemapping) { ASSERT_EQ(fds_after, fds_before); int ret; - ret = HANDLE_EINTR(close(sockets[0])); + ret = IGNORE_EINTR(close(sockets[0])); DPCHECK(ret == 0); - ret = HANDLE_EINTR(close(sockets[1])); + ret = IGNORE_EINTR(close(sockets[1])); DPCHECK(ret == 0); - ret = HANDLE_EINTR(close(dev_null)); + ret = IGNORE_EINTR(close(dev_null)); DPCHECK(ret == 0); } @@ -618,13 +585,13 @@ std::string TestLaunchProcess(const base::EnvironmentMap& env_changes, CHECK_EQ(0, clone_flags); #endif // OS_LINUX EXPECT_TRUE(base::LaunchProcess(args, options, NULL)); - PCHECK(HANDLE_EINTR(close(fds[1])) == 0); + PCHECK(IGNORE_EINTR(close(fds[1])) == 0); char buf[512]; const ssize_t n = HANDLE_EINTR(read(fds[0], buf, sizeof(buf))); PCHECK(n > 0); - PCHECK(HANDLE_EINTR(close(fds[0])) == 0); + PCHECK(IGNORE_EINTR(close(fds[0])) == 0); return std::string(buf, n); } @@ -775,7 +742,16 @@ TEST_F(ProcessUtilTest, GetAppOutputRestrictedSIGPIPE) { } #endif -TEST_F(ProcessUtilTest, GetAppOutputRestrictedNoZombies) { +#if defined(ADDRESS_SANITIZER) && defined(OS_MACOSX) && \ + defined(ARCH_CPU_64_BITS) +// Times out under AddressSanitizer on 64-bit OS X, see +// http://crbug.com/298197. +#define MAYBE_GetAppOutputRestrictedNoZombies \ + DISABLED_GetAppOutputRestrictedNoZombies +#else +#define MAYBE_GetAppOutputRestrictedNoZombies GetAppOutputRestrictedNoZombies +#endif +TEST_F(ProcessUtilTest, MAYBE_GetAppOutputRestrictedNoZombies) { std::vector<std::string> argv; argv.push_back(std::string(kShellPath)); // argv[0] @@ -826,50 +802,6 @@ TEST_F(ProcessUtilTest, GetParentProcessId) { EXPECT_EQ(ppid, getppid()); } -#if defined(OS_LINUX) || defined(OS_ANDROID) -TEST_F(ProcessUtilTest, ParseProcStatCPU) { - // /proc/self/stat for a process running "top". - const char kTopStat[] = "960 (top) S 16230 960 16230 34818 960 " - "4202496 471 0 0 0 " - "12 16 0 0 " // <- These are the goods. - "20 0 1 0 121946157 15077376 314 18446744073709551615 4194304 " - "4246868 140733983044336 18446744073709551615 140244213071219 " - "0 0 0 138047495 0 0 0 17 1 0 0 0 0 0"; - EXPECT_EQ(12 + 16, base::ParseProcStatCPU(kTopStat)); - - // cat /proc/self/stat on a random other machine I have. - const char kSelfStat[] = "5364 (cat) R 5354 5364 5354 34819 5364 " - "0 142 0 0 0 " - "0 0 0 0 " // <- No CPU, apparently. - "16 0 1 0 1676099790 2957312 114 4294967295 134512640 134528148 " - "3221224832 3221224344 3086339742 0 0 0 0 0 0 0 17 0 0 0"; - - EXPECT_EQ(0, base::ParseProcStatCPU(kSelfStat)); -} - -// Disable on Android because base_unittests runs inside a Dalvik VM that -// starts and stop threads (crbug.com/175563). -#if !defined(OS_ANDROID) -TEST_F(ProcessUtilTest, GetNumberOfThreads) { - const base::ProcessHandle current = base::GetCurrentProcessHandle(); - const int initial_threads = base::GetNumberOfThreads(current); - ASSERT_GT(initial_threads, 0); - const int kNumAdditionalThreads = 10; - { - scoped_ptr<base::Thread> my_threads[kNumAdditionalThreads]; - for (int i = 0; i < kNumAdditionalThreads; ++i) { - my_threads[i].reset(new base::Thread("GetNumberOfThreadsTest")); - my_threads[i]->Start(); - ASSERT_EQ(base::GetNumberOfThreads(current), initial_threads + 1 + i); - } - } - // The Thread destructor will stop them. - ASSERT_EQ(initial_threads, base::GetNumberOfThreads(current)); -} -#endif // !defined(OS_ANDROID) - -#endif // defined(OS_LINUX) || defined(OS_ANDROID) - // TODO(port): port those unit tests. bool IsProcessDead(base::ProcessHandle child) { // waitpid() will actually reap the process which is exactly NOT what we diff --git a/chromium/base/rand_util.cc b/chromium/base/rand_util.cc index da6de87dc34..9641ff4f9c7 100644 --- a/chromium/base/rand_util.cc +++ b/chromium/base/rand_util.cc @@ -6,6 +6,7 @@ #include <math.h> +#include <algorithm> #include <limits> #include "base/basictypes.h" diff --git a/chromium/base/rand_util_posix.cc b/chromium/base/rand_util_posix.cc index 9b18777b990..082d64923d5 100644 --- a/chromium/base/rand_util_posix.cc +++ b/chromium/base/rand_util_posix.cc @@ -46,9 +46,8 @@ uint64 RandUint64() { uint64 number; int urandom_fd = g_urandom_fd.Pointer()->fd(); - bool success = file_util::ReadFromFD(urandom_fd, - reinterpret_cast<char*>(&number), - sizeof(number)); + bool success = ReadFromFD(urandom_fd, reinterpret_cast<char*>(&number), + sizeof(number)); CHECK(success); return number; diff --git a/chromium/base/run_loop.cc b/chromium/base/run_loop.cc index 2e80ff29e01..8666ee448fe 100644 --- a/chromium/base/run_loop.cc +++ b/chromium/base/run_loop.cc @@ -10,13 +10,13 @@ namespace base { RunLoop::RunLoop() : loop_(MessageLoop::current()), - weak_factory_(this), previous_run_loop_(NULL), run_depth_(0), run_called_(false), quit_called_(false), running_(false), - quit_when_idle_received_(false) { + quit_when_idle_received_(false), + weak_factory_(this) { #if !defined(OS_MACOSX) && !defined(OS_ANDROID) && \ !defined(USE_GTK_MESSAGE_PUMP) dispatcher_ = NULL; @@ -27,14 +27,14 @@ RunLoop::RunLoop() !defined(USE_GTK_MESSAGE_PUMP) RunLoop::RunLoop(MessageLoop::Dispatcher* dispatcher) : loop_(MessageLoop::current()), - weak_factory_(this), previous_run_loop_(NULL), dispatcher_(dispatcher), run_depth_(0), run_called_(false), quit_called_(false), running_(false), - quit_when_idle_received_(false) { + quit_when_idle_received_(false), + weak_factory_(this) { } #endif diff --git a/chromium/base/run_loop.h b/chromium/base/run_loop.h index b1679073a11..055b1b8146d 100644 --- a/chromium/base/run_loop.h +++ b/chromium/base/run_loop.h @@ -97,9 +97,6 @@ class BASE_EXPORT RunLoop { MessageLoop* loop_; - // WeakPtrFactory for QuitClosure safety. - base::WeakPtrFactory<RunLoop> weak_factory_; - // Parent RunLoop or NULL if this is the top-most RunLoop. RunLoop* previous_run_loop_; @@ -119,6 +116,9 @@ class BASE_EXPORT RunLoop { // that we should quit Run once it becomes idle. bool quit_when_idle_received_; + // WeakPtrFactory for QuitClosure safety. + base::WeakPtrFactory<RunLoop> weak_factory_; + DISALLOW_COPY_AND_ASSIGN(RunLoop); }; diff --git a/chromium/base/scoped_native_library_unittest.cc b/chromium/base/scoped_native_library_unittest.cc index c120555ec69..035faa03f74 100644 --- a/chromium/base/scoped_native_library_unittest.cc +++ b/chromium/base/scoped_native_library_unittest.cc @@ -18,19 +18,25 @@ TEST(ScopedNativeLibrary, Basic) { // Get the pointer to DirectDrawCreate() from "ddraw.dll" and verify it // is valid only in this scope. // FreeLibrary() doesn't actually unload a DLL until its reference count - // becomes zero, i.e. this function pointer is still valid if the DLL used + // becomes zero, i.e. function pointer is still valid if the DLL used // in this test is also used by another part of this executable. // So, this test uses "ddraw.dll", which is not used by Chrome at all but // installed on all versions of Windows. - FARPROC test_function; + const char kFunctionName[] = "DirectDrawCreate"; + NativeLibrary native_library; { FilePath path(GetNativeLibraryName(L"ddraw")); - ScopedNativeLibrary library(path); - test_function = reinterpret_cast<FARPROC>( - library.GetFunctionPointer("DirectDrawCreate")); + native_library = LoadNativeLibrary(path, NULL); + ScopedNativeLibrary library(native_library); + FARPROC test_function = + reinterpret_cast<FARPROC>(library.GetFunctionPointer(kFunctionName)); EXPECT_EQ(0, IsBadCodePtr(test_function)); + EXPECT_EQ( + GetFunctionPointerFromNativeLibrary(native_library, kFunctionName), + test_function); } - EXPECT_NE(0, IsBadCodePtr(test_function)); + EXPECT_EQ(NULL, + GetFunctionPointerFromNativeLibrary(native_library, kFunctionName)); #endif } diff --git a/chromium/base/security_unittest.cc b/chromium/base/security_unittest.cc index 5b266b0a11c..cf3b1296667 100644 --- a/chromium/base/security_unittest.cc +++ b/chromium/base/security_unittest.cc @@ -75,14 +75,11 @@ bool IsTcMallocBypassed() { } bool CallocDiesOnOOM() { +// The sanitizers' calloc dies on OOM instead of returning NULL. // The wrapper function in base/process_util_linux.cc that is used when we // compile without TCMalloc will just die on OOM instead of returning NULL. -// This function is explicitly disabled if we compile with AddressSanitizer, -// MemorySanitizer or ThreadSanitizer. -#if defined(OS_LINUX) && defined(NO_TCMALLOC) && \ - (!defined(ADDRESS_SANITIZER) && \ - !defined(MEMORY_SANITIZER) && \ - !defined(THREAD_SANITIZER)) +#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \ + defined(THREAD_SANITIZER) || (defined(OS_LINUX) && defined(NO_TCMALLOC)) return true; #else return false; @@ -150,10 +147,10 @@ TEST(SecurityTest, TCMALLOC_TEST(MemoryAllocationRestrictionsNewArray)) { // The tests bellow check for overflows in new[] and calloc(). -#if defined(OS_IOS) || defined(OS_WIN) - #define DISABLE_ON_IOS_AND_WIN(function) DISABLED_##function +#if defined(OS_IOS) || defined(OS_WIN) || defined(THREAD_SANITIZER) + #define DISABLE_ON_IOS_AND_WIN_AND_TSAN(function) DISABLED_##function #else - #define DISABLE_ON_IOS_AND_WIN(function) function + #define DISABLE_ON_IOS_AND_WIN_AND_TSAN(function) function #endif // There are platforms where these tests are known to fail. We would like to @@ -177,7 +174,7 @@ void OverflowTestsSoftExpectTrue(bool overflow_detected) { // Test array[TooBig][X] and array[X][TooBig] allocations for int overflows. // IOS doesn't honor nothrow, so disable the test there. // Crashes on Windows Dbg builds, disable there as well. -TEST(SecurityTest, DISABLE_ON_IOS_AND_WIN(NewOverflow)) { +TEST(SecurityTest, DISABLE_ON_IOS_AND_WIN_AND_TSAN(NewOverflow)) { const size_t kArraySize = 4096; // We want something "dynamic" here, so that the compiler doesn't // immediately reject crazy arrays. @@ -233,19 +230,6 @@ TEST(SecurityTest, CallocOverflow) { } #if (defined(OS_LINUX) || defined(OS_CHROMEOS)) && defined(__x86_64__) -// Useful for debugging. -void PrintProcSelfMaps() { - int fd = open("/proc/self/maps", O_RDONLY); - file_util::ScopedFD fd_closer(&fd); - ASSERT_GE(fd, 0); - char buffer[1<<13]; - int ret; - ret = read(fd, buffer, sizeof(buffer) - 1); - ASSERT_GT(ret, 0); - buffer[ret - 1] = 0; - fprintf(stdout, "%s\n", buffer); -} - // Check if ptr1 and ptr2 are separated by less than size chars. bool ArePointersToSameArea(void* ptr1, void* ptr2, size_t size) { ptrdiff_t ptr_diff = reinterpret_cast<char*>(std::max(ptr1, ptr2)) - diff --git a/chromium/base/sequence_checker.h b/chromium/base/sequence_checker.h index 89bbd7eca92..40d535e12d2 100644 --- a/chromium/base/sequence_checker.h +++ b/chromium/base/sequence_checker.h @@ -44,7 +44,7 @@ class SequenceCheckerDoNothing { // class MyClass { // public: // void Foo() { -// DCHECK(sequence_checker_.CalledOnValidSequence()); +// DCHECK(sequence_checker_.CalledOnValidSequencedThread()); // ... (do stuff) ... // } // diff --git a/chromium/base/strings/safe_sprintf.cc b/chromium/base/strings/safe_sprintf.cc index 2d4d93c947b..1e09b6e899e 100644 --- a/chromium/base/strings/safe_sprintf.cc +++ b/chromium/base/strings/safe_sprintf.cc @@ -107,12 +107,13 @@ class Buffer { : buffer_(buffer), size_(size - 1), // Account for trailing NUL byte count_(0) { -// This test should work on all C++11 compilers, but apparently something is -// not working on all versions of clang just yet (e.g. on Mac, IOS, and -// Android). We are conservative and exclude all of clang for the time being. -// TODO(markus): Check if this restriction can be lifted. -#if __cplusplus >= 201103 && !defined(__clang__) - COMPILE_ASSERT(kSSizeMaxConst == std::numeric_limits<ssize_t>::max(), +// The following assertion does not build on Mac and Android. This is because +// static_assert only works with compile-time constants, but mac uses +// libstdc++4.2 and android uses stlport, which both don't mark +// numeric_limits::max() as constexp. +#if __cplusplus >= 201103 && !defined(OS_ANDROID) && !defined(OS_MACOSX) && !defined(OS_IOS) + COMPILE_ASSERT(kSSizeMaxConst == \ + static_cast<size_t>(std::numeric_limits<ssize_t>::max()), kSSizeMax_is_the_max_value_of_an_ssize_t); #endif DEBUG_CHECK(size > 0); diff --git a/chromium/base/strings/safe_sprintf.h b/chromium/base/strings/safe_sprintf.h index a1be047955c..c9ee7173753 100644 --- a/chromium/base/strings/safe_sprintf.h +++ b/chromium/base/strings/safe_sprintf.h @@ -104,8 +104,6 @@ typedef long ssize_t; // like SafeSPrintf(buf, "%p %d", 1, 2) results in "%p 2"). See above for // the use of RAW_CHECK() in debug builds, though. // -// The pre-C++11 version cannot handle more than ten arguments. -// // Basic example: // char buf[20]; // base::strings::SafeSPrintf(buf, "The answer: %2d", 42); @@ -190,29 +188,8 @@ BASE_EXPORT size_t GetSafeSPrintfSSizeMaxForTest(); } // namespace internal -#if __cplusplus >= 201103 // C++11 - -template<typename... Args> -ssize_t SafeSNPrintf(char* buf, size_t N, const char* fmt, Args... args) { - // Use Arg() object to record type information and then copy arguments to an - // array to make it easier to iterate over them. - const internal::Arg arg_array[] = { args... }; - return internal::SafeSNPrintf(buf, N, fmt, arg_array, arraysize(arg_array)); -} - -template<size_t N, typename... Args> -ssize_t SafeSPrintf(char (&buf)[N], const char* fmt, Args... args) { - // Use Arg() object to record type information and then copy arguments to an - // array to make it easier to iterate over them. - const internal::Arg arg_array[] = { args... }; - return internal::SafeSNPrintf(buf, N, fmt, arg_array, arraysize(arg_array)); -} - -#else // Pre-C++11 - // TODO(markus): C++11 has a much more concise and readable solution for -// expressing what we are doing here. Delete the fall-back code for older -// compilers as soon as we have fully switched to C++11. +// expressing what we are doing here. template<class T0, class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9> @@ -426,7 +403,6 @@ ssize_t SafeSPrintf(char (&buf)[N], const char* fmt, T0 arg0) { const internal::Arg arg_array[] = { arg0 }; return internal::SafeSNPrintf(buf, N, fmt, arg_array, arraysize(arg_array)); } -#endif // Fast-path when we don't actually need to substitute any arguments. BASE_EXPORT ssize_t SafeSNPrintf(char* buf, size_t N, const char* fmt); diff --git a/chromium/base/strings/safe_sprintf_unittest.cc b/chromium/base/strings/safe_sprintf_unittest.cc index 0e142351103..937fa4eca23 100644 --- a/chromium/base/strings/safe_sprintf_unittest.cc +++ b/chromium/base/strings/safe_sprintf_unittest.cc @@ -264,18 +264,6 @@ TEST(SafeSPrintfTest, NArgs) { EXPECT_EQ(10, SafeSNPrintf(buf, 11, "%c%c%c%c%c%c%c%c%c%c", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); EXPECT_EQ("\1\2\3\4\5\6\7\10\11\12", std::string(buf)); - - - // C++11 is smart enough to handle variadic template arguments. It can - // deal with arbitrary numbers of arguments. -#if __cplusplus >= 201103 // C++11 - EXPECT_EQ(11, SafeSPrintf(buf, "%c%c%c%c%c%c%c%c%c%c%c", - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)); - EXPECT_EQ("\1\2\3\4\5\6\7\10\11\12\13", std::string(buf)); - EXPECT_EQ(11, SafeSNPrintf(buf, 12, "%c%c%c%c%c%c%c%c%c%c%c", - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)); - EXPECT_EQ("\1\2\3\4\5\6\7\10\11\12\13", std::string(buf)); -#endif } TEST(SafeSPrintfTest, DataTypes) { diff --git a/chromium/base/strings/string16.cc b/chromium/base/strings/string16.cc index c802eef128f..f4c8cf74607 100644 --- a/chromium/base/strings/string16.cc +++ b/chromium/base/strings/string16.cc @@ -77,6 +77,6 @@ void PrintTo(const string16& str, std::ostream* out) { } // namespace base -template class std::basic_string<char16, base::string16_char_traits>; +template class std::basic_string<base::char16, base::string16_char_traits>; #endif // WCHAR_T_IS_UTF32 diff --git a/chromium/base/strings/string16_unittest.cc b/chromium/base/strings/string16_unittest.cc index d98b2a9ec5d..4e582181a86 100644 --- a/chromium/base/strings/string16_unittest.cc +++ b/chromium/base/strings/string16_unittest.cc @@ -9,6 +9,8 @@ #include "base/strings/utf_string_conversions.h" #include "testing/gtest/include/gtest/gtest.h" +namespace base { + #if defined(WCHAR_T_IS_UTF32) // We define a custom operator<< for string16 so we can use it with logging. @@ -52,3 +54,5 @@ TEST(String16Test, OutputStream) { } #endif + +} // namespace base diff --git a/chromium/base/strings/string_number_conversions.cc b/chromium/base/strings/string_number_conversions.cc index b9412d93131..e3e71bef023 100644 --- a/chromium/base/strings/string_number_conversions.cc +++ b/chromium/base/strings/string_number_conversions.cc @@ -301,6 +301,11 @@ class BaseHexIteratorRangeToIntTraits }; template<typename ITERATOR> +class BaseHexIteratorRangeToUIntTraits + : public BaseIteratorRangeToNumberTraits<ITERATOR, uint32, 16> { +}; + +template<typename ITERATOR> class BaseHexIteratorRangeToInt64Traits : public BaseIteratorRangeToNumberTraits<ITERATOR, int64, 16> { }; @@ -313,6 +318,9 @@ class BaseHexIteratorRangeToUInt64Traits typedef BaseHexIteratorRangeToIntTraits<StringPiece::const_iterator> HexIteratorRangeToIntTraits; +typedef BaseHexIteratorRangeToUIntTraits<StringPiece::const_iterator> + HexIteratorRangeToUIntTraits; + typedef BaseHexIteratorRangeToInt64Traits<StringPiece::const_iterator> HexIteratorRangeToInt64Traits; @@ -499,6 +507,11 @@ bool HexStringToInt(const StringPiece& input, int* output) { input.begin(), input.end(), output); } +bool HexStringToUInt(const StringPiece& input, uint32* output) { + return IteratorRangeToNumber<HexIteratorRangeToUIntTraits>::Invoke( + input.begin(), input.end(), output); +} + bool HexStringToInt64(const StringPiece& input, int64* output) { return IteratorRangeToNumber<HexIteratorRangeToInt64Traits>::Invoke( input.begin(), input.end(), output); diff --git a/chromium/base/strings/string_number_conversions.h b/chromium/base/strings/string_number_conversions.h index b199bb5f7a9..6f5df4a093f 100644 --- a/chromium/base/strings/string_number_conversions.h +++ b/chromium/base/strings/string_number_conversions.h @@ -101,6 +101,12 @@ BASE_EXPORT bool HexStringToInt(const StringPiece& input, int* output); // Best effort conversion, see StringToInt above for restrictions. // Will only successful parse hex values that will fit into |output|, i.e. +// 0x00000000 < |input| < 0xFFFFFFFF. +// The string is not required to start with 0x. +BASE_EXPORT bool HexStringToUInt(const StringPiece& input, uint32* output); + +// Best effort conversion, see StringToInt above for restrictions. +// Will only successful parse hex values that will fit into |output|, i.e. // -0x8000000000000000 < |input| < 0x7FFFFFFFFFFFFFFF. BASE_EXPORT bool HexStringToInt64(const StringPiece& input, int64* output); diff --git a/chromium/base/strings/string_number_conversions_unittest.cc b/chromium/base/strings/string_number_conversions_unittest.cc index b8619647de5..b6e5f961ce2 100644 --- a/chromium/base/strings/string_number_conversions_unittest.cc +++ b/chromium/base/strings/string_number_conversions_unittest.cc @@ -458,6 +458,66 @@ TEST(StringNumberConversionsTest, HexStringToInt) { EXPECT_EQ(0xc0ffee, output); } +TEST(StringNumberConversionsTest, HexStringToUInt) { + static const struct { + std::string input; + uint32 output; + bool success; + } cases[] = { + {"0", 0, true}, + {"42", 0x42, true}, + {"-42", 0, false}, + {"+42", 0x42, true}, + {"7fffffff", INT_MAX, true}, + {"-80000000", 0, false}, + {"ffffffff", 0xffffffff, true}, + {"DeadBeef", 0xdeadbeef, true}, + {"0x42", 0x42, true}, + {"-0x42", 0, false}, + {"+0x42", 0x42, true}, + {"0x7fffffff", INT_MAX, true}, + {"-0x80000000", 0, false}, + {"0xffffffff", kuint32max, true}, + {"0XDeadBeef", 0xdeadbeef, true}, + {"0x7fffffffffffffff", kuint32max, false}, // Overflow test. + {"-0x8000000000000000", 0, false}, + {"0x8000000000000000", kuint32max, false}, // Overflow test. + {"-0x8000000000000001", 0, false}, + {"0xFFFFFFFFFFFFFFFF", kuint32max, false}, // Overflow test. + {"FFFFFFFFFFFFFFFF", kuint32max, false}, // Overflow test. + {"0x0000000000000000", 0, true}, + {"0000000000000000", 0, true}, + {"1FFFFFFFFFFFFFFFF", kuint32max, false}, // Overflow test. + {"0x0f", 0x0f, true}, + {"0f", 0x0f, true}, + {" 45", 0x45, false}, + {"\t\n\v\f\r 0x45", 0x45, false}, + {" 45", 0x45, false}, + {"45 ", 0x45, false}, + {"45:", 0x45, false}, + {"efgh", 0xef, false}, + {"0xefgh", 0xef, false}, + {"hgfe", 0, false}, + {"-", 0, false}, + {"", 0, false}, + {"0x", 0, false}, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { + uint32 output = 0; + EXPECT_EQ(cases[i].success, HexStringToUInt(cases[i].input, &output)); + EXPECT_EQ(cases[i].output, output); + } + // One additional test to verify that conversion of numbers in strings with + // embedded NUL characters. The NUL and extra data after it should be + // interpreted as junk after the number. + const char input[] = "0xc0ffee\09"; + std::string input_string(input, arraysize(input) - 1); + uint32 output; + EXPECT_FALSE(HexStringToUInt(input_string, &output)); + EXPECT_EQ(0xc0ffeeU, output); +} + TEST(StringNumberConversionsTest, HexStringToInt64) { static const struct { std::string input; diff --git a/chromium/base/strings/string_split.h b/chromium/base/strings/string_split.h index faf08d6985d..7c27e4899da 100644 --- a/chromium/base/strings/string_split.h +++ b/chromium/base/strings/string_split.h @@ -14,9 +14,9 @@ namespace base { -// Splits |str| into a vector of strings delimited by |s|, placing the results -// in |r|. If several instances of |s| are contiguous, or if |str| begins with -// or ends with |s|, then an empty string is inserted. +// Splits |str| into a vector of strings delimited by |c|, placing the results +// in |r|. If several instances of |c| are contiguous, or if |str| begins with +// or ends with |c|, then an empty string is inserted. // // Every substring is trimmed of any leading or trailing white space. // NOTE: |c| must be in BMP (Basic Multilingual Plane) diff --git a/chromium/base/strings/string_util.cc b/chromium/base/strings/string_util.cc index 3ed706978e2..cee124b0c1a 100644 --- a/chromium/base/strings/string_util.cc +++ b/chromium/base/strings/string_util.cc @@ -26,6 +26,10 @@ #include "base/third_party/icu/icu_utf.h" #include "build/build_config.h" +// Remove when this entire file is in the base namespace. +using base::char16; +using base::string16; + namespace { // Force the singleton used by Empty[W]String[16] to be a unique type. This @@ -100,9 +104,6 @@ bool IsWprintfFormatPortable(const wchar_t* format) { return true; } -} // namespace base - - const std::string& EmptyString() { return EmptyStrings::GetInstance()->s; } @@ -193,19 +194,11 @@ TrimPositions TrimStringT(const STR& input, ((last_good_char == last_char) ? TRIM_NONE : TRIM_TRAILING)); } -bool TrimString(const std::wstring& input, - const wchar_t trim_chars[], - std::wstring* output) { - return TrimStringT(input, trim_chars, TRIM_ALL, output) != TRIM_NONE; -} - -#if !defined(WCHAR_T_IS_UTF16) bool TrimString(const string16& input, const char16 trim_chars[], string16* output) { return TrimStringT(input, trim_chars, TRIM_ALL, output) != TRIM_NONE; } -#endif bool TrimString(const std::string& input, const char trim_chars[], @@ -235,8 +228,8 @@ void TruncateUTF8ToByteSize(const std::string& input, int32 prev = char_index; uint32 code_point = 0; CBU8_NEXT(data, char_index, truncation_length, code_point); - if (!base::IsValidCharacter(code_point) || - !base::IsValidCodepoint(code_point)) { + if (!IsValidCharacter(code_point) || + !IsValidCodepoint(code_point)) { char_index = prev - 1; } else { break; @@ -249,16 +242,18 @@ void TruncateUTF8ToByteSize(const std::string& input, output->clear(); } -TrimPositions TrimWhitespace(const string16& input, +} // namespace base + +TrimPositions TrimWhitespace(const base::string16& input, TrimPositions positions, - string16* output) { - return TrimStringT(input, kWhitespaceUTF16, positions, output); + base::string16* output) { + return base::TrimStringT(input, base::kWhitespaceUTF16, positions, output); } TrimPositions TrimWhitespaceASCII(const std::string& input, TrimPositions positions, std::string* output) { - return TrimStringT(input, kWhitespaceASCII, positions, output); + return base::TrimStringT(input, base::kWhitespaceASCII, positions, output); } // This function is only for backward-compatibility. @@ -311,17 +306,10 @@ STR CollapseWhitespaceT(const STR& text, return result; } -std::wstring CollapseWhitespace(const std::wstring& text, - bool trim_sequences_with_line_breaks) { - return CollapseWhitespaceT(text, trim_sequences_with_line_breaks); -} - -#if !defined(WCHAR_T_IS_UTF16) string16 CollapseWhitespace(const string16& text, bool trim_sequences_with_line_breaks) { return CollapseWhitespaceT(text, trim_sequences_with_line_breaks); } -#endif std::string CollapseWhitespaceASCII(const std::string& text, bool trim_sequences_with_line_breaks) { @@ -336,8 +324,8 @@ bool ContainsOnlyWhitespaceASCII(const std::string& str) { return true; } -bool ContainsOnlyWhitespace(const string16& str) { - return str.find_first_not_of(kWhitespaceUTF16) == string16::npos; +bool ContainsOnlyWhitespace(const base::string16& str) { + return str.find_first_not_of(base::kWhitespaceUTF16) == string16::npos; } template<typename STR> @@ -350,22 +338,19 @@ static bool ContainsOnlyCharsT(const STR& input, const STR& characters) { return true; } -bool ContainsOnlyChars(const std::wstring& input, - const std::wstring& characters) { - return ContainsOnlyCharsT(input, characters); -} - -#if !defined(WCHAR_T_IS_UTF16) bool ContainsOnlyChars(const string16& input, const string16& characters) { return ContainsOnlyCharsT(input, characters); } -#endif bool ContainsOnlyChars(const std::string& input, const std::string& characters) { return ContainsOnlyCharsT(input, characters); } +#if !defined(WCHAR_T_IS_UTF16) +bool IsStringASCII(const std::wstring& str); +#endif + std::string WideToASCII(const std::wstring& wide) { DCHECK(IsStringASCII(wide)) << wide; return std::string(wide.begin(), wide.end()); @@ -376,20 +361,6 @@ std::string UTF16ToASCII(const string16& utf16) { return std::string(utf16.begin(), utf16.end()); } -// Latin1 is just the low range of Unicode, so we can copy directly to convert. -bool WideToLatin1(const std::wstring& wide, std::string* latin1) { - std::string output; - output.resize(wide.size()); - latin1->clear(); - for (size_t i = 0; i < wide.size(); i++) { - if (wide[i] > 255) - return false; - output[i] = static_cast<char>(wide[i]); - } - latin1->swap(output); - return true; -} - template<class STR> static bool DoIsStringASCII(const STR& str) { for (size_t i = 0; i < str.length(); i++) { @@ -400,15 +371,15 @@ static bool DoIsStringASCII(const STR& str) { return true; } +#if !defined(WCHAR_T_IS_UTF16) bool IsStringASCII(const std::wstring& str) { return DoIsStringASCII(str); } +#endif -#if !defined(WCHAR_T_IS_UTF16) bool IsStringASCII(const string16& str) { return DoIsStringASCII(str); } -#endif bool IsStringASCII(const base::StringPiece& str) { return DoIsStringASCII(str); @@ -444,15 +415,9 @@ bool LowerCaseEqualsASCII(const std::string& a, const char* b) { return DoLowerCaseEqualsASCII(a.begin(), a.end(), b); } -bool LowerCaseEqualsASCII(const std::wstring& a, const char* b) { - return DoLowerCaseEqualsASCII(a.begin(), a.end(), b); -} - -#if !defined(WCHAR_T_IS_UTF16) bool LowerCaseEqualsASCII(const string16& a, const char* b) { return DoLowerCaseEqualsASCII(a.begin(), a.end(), b); } -#endif bool LowerCaseEqualsASCII(std::string::const_iterator a_begin, std::string::const_iterator a_end, @@ -460,19 +425,11 @@ bool LowerCaseEqualsASCII(std::string::const_iterator a_begin, return DoLowerCaseEqualsASCII(a_begin, a_end, b); } -bool LowerCaseEqualsASCII(std::wstring::const_iterator a_begin, - std::wstring::const_iterator a_end, - const char* b) { - return DoLowerCaseEqualsASCII(a_begin, a_end, b); -} - -#if !defined(WCHAR_T_IS_UTF16) bool LowerCaseEqualsASCII(string16::const_iterator a_begin, string16::const_iterator a_end, const char* b) { return DoLowerCaseEqualsASCII(a_begin, a_end, b); } -#endif // TODO(port): Resolve wchar_t/iterator issues that require OS_ANDROID here. #if !defined(OS_ANDROID) @@ -482,19 +439,11 @@ bool LowerCaseEqualsASCII(const char* a_begin, return DoLowerCaseEqualsASCII(a_begin, a_end, b); } -bool LowerCaseEqualsASCII(const wchar_t* a_begin, - const wchar_t* a_end, - const char* b) { - return DoLowerCaseEqualsASCII(a_begin, a_end, b); -} - -#if !defined(WCHAR_T_IS_UTF16) bool LowerCaseEqualsASCII(const char16* a_begin, const char16* a_end, const char* b) { return DoLowerCaseEqualsASCII(a_begin, a_end, b); } -#endif #endif // !defined(OS_ANDROID) @@ -525,17 +474,10 @@ bool StartsWithT(const STR& str, const STR& search, bool case_sensitive) { } } -bool StartsWith(const std::wstring& str, const std::wstring& search, - bool case_sensitive) { - return StartsWithT(str, search, case_sensitive); -} - -#if !defined(WCHAR_T_IS_UTF16) bool StartsWith(const string16& str, const string16& search, bool case_sensitive) { return StartsWithT(str, search, case_sensitive); } -#endif template <typename STR> bool EndsWithT(const STR& str, const STR& search, bool case_sensitive) { @@ -557,17 +499,10 @@ bool EndsWith(const std::string& str, const std::string& search, return EndsWithT(str, search, case_sensitive); } -bool EndsWith(const std::wstring& str, const std::wstring& search, - bool case_sensitive) { - return EndsWithT(str, search, case_sensitive); -} - -#if !defined(WCHAR_T_IS_UTF16) bool EndsWith(const string16& str, const string16& search, bool case_sensitive) { return EndsWithT(str, search, case_sensitive); } -#endif static const char* const kByteStringsUnlocalized[] = { " B", @@ -674,19 +609,11 @@ static size_t TokenizeT(const STR& str, return tokens->size(); } -size_t Tokenize(const std::wstring& str, - const std::wstring& delimiters, - std::vector<std::wstring>* tokens) { - return TokenizeT(str, delimiters, tokens); -} - -#if !defined(WCHAR_T_IS_UTF16) size_t Tokenize(const string16& str, const string16& delimiters, std::vector<string16>* tokens) { return TokenizeT(str, delimiters, tokens); } -#endif size_t Tokenize(const std::string& str, const std::string& delimiters, @@ -817,10 +744,9 @@ string16 ReplaceStringPlaceholders(const string16& format_string, subst.push_back(a); string16 result = ReplaceStringPlaceholders(format_string, subst, &offsets); - DCHECK(offsets.size() == 1); - if (offset) { + DCHECK_EQ(1U, offsets.size()); + if (offset) *offset = offsets[0]; - } return result; } diff --git a/chromium/base/strings/string_util.h b/chromium/base/strings/string_util.h index d2a216efaf6..7b4b2193abf 100644 --- a/chromium/base/strings/string_util.h +++ b/chromium/base/strings/string_util.h @@ -47,14 +47,6 @@ int strncmp16(const char16* s1, const char16* s2, size_t count); int vsnprintf(char* buffer, size_t size, const char* format, va_list arguments) PRINTF_FORMAT(3, 0); -// vswprintf always null-terminates, but when truncation occurs, it will either -// return -1 or the number of characters that would be in an untruncated -// formatted string. The actual return value depends on the underlying -// C library's vswprintf implementation. -int vswprintf(wchar_t* buffer, size_t size, - const wchar_t* format, va_list arguments) - WPRINTF_FORMAT(3, 0); - // Some of these implementations need to be inlined. // We separate the declaration from the implementation of this inline @@ -69,18 +61,6 @@ inline int snprintf(char* buffer, size_t size, const char* format, ...) { return result; } -// We separate the declaration from the implementation of this inline -// function just so the WPRINTF_FORMAT works. -inline int swprintf(wchar_t* buffer, size_t size, const wchar_t* format, ...) - WPRINTF_FORMAT(3, 4); -inline int swprintf(wchar_t* buffer, size_t size, const wchar_t* format, ...) { - va_list arguments; - va_start(arguments, format); - int result = vswprintf(buffer, size, format, arguments); - va_end(arguments); - return result; -} - // BSD-style safe and consistent string copy functions. // Copies |src| to |dst|, where |dst_size| is the total allocated size of |dst|. // Copies at most |dst_size|-1 characters, and always NULL terminates |dst|, as @@ -143,33 +123,29 @@ template<typename Char> struct CaseInsensitiveCompareASCII { } }; -} // namespace base - -#if defined(OS_WIN) -#include "base/strings/string_util_win.h" -#elif defined(OS_POSIX) -#include "base/strings/string_util_posix.h" -#else -#error Define string operations appropriately for your platform -#endif - // These threadsafe functions return references to globally unique empty // strings. // -// DO NOT USE THESE AS A GENERAL-PURPOSE SUBSTITUTE FOR DEFAULT CONSTRUCTORS. -// There is only one case where you should use these: functions which need to -// return a string by reference (e.g. as a class member accessor), and don't -// have an empty string to use (e.g. in an error case). These should not be -// used as initializers, function arguments, or return values for functions -// which return by value or outparam. +// It is likely faster to construct a new empty string object (just a few +// instructions to set the length to 0) than to get the empty string singleton +// returned by these functions (which requires threadsafe singleton access). +// +// Therefore, DO NOT USE THESE AS A GENERAL-PURPOSE SUBSTITUTE FOR DEFAULT +// CONSTRUCTORS. There is only one case where you should use these: functions +// which need to return a string by reference (e.g. as a class member +// accessor), and don't have an empty string to use (e.g. in an error case). +// These should not be used as initializers, function arguments, or return +// values for functions which return by value or outparam. BASE_EXPORT const std::string& EmptyString(); -BASE_EXPORT const std::wstring& EmptyWString(); BASE_EXPORT const string16& EmptyString16(); +// Contains the set of characters representing whitespace in the corresponding +// encoding. Null-terminated. BASE_EXPORT extern const wchar_t kWhitespaceWide[]; BASE_EXPORT extern const char16 kWhitespaceUTF16[]; BASE_EXPORT extern const char kWhitespaceASCII[]; +// Null-terminated string representing the UTF-8 byte order mark. BASE_EXPORT extern const char kUtf8ByteOrderMark[]; // Removes characters in |remove_chars| from anywhere in |input|. Returns true @@ -199,9 +175,6 @@ BASE_EXPORT bool ReplaceChars(const std::string& input, // Removes characters in |trim_chars| from the beginning and end of |input|. // |trim_chars| must be null-terminated. // NOTE: Safe to use the same variable for both |input| and |output|. -BASE_EXPORT bool TrimString(const std::wstring& input, - const wchar_t trim_chars[], - std::wstring* output); BASE_EXPORT bool TrimString(const string16& input, const char16 trim_chars[], string16* output); @@ -215,6 +188,16 @@ BASE_EXPORT void TruncateUTF8ToByteSize(const std::string& input, const size_t byte_size, std::string* output); +} // namespace base + +#if defined(OS_WIN) +#include "base/strings/string_util_win.h" +#elif defined(OS_POSIX) +#include "base/strings/string_util_posix.h" +#else +#error Define string operations appropriately for your platform +#endif + // Trims any whitespace from either end of the input string. Returns where // whitespace was found. // The non-wide version has two functions: @@ -228,9 +211,9 @@ enum TrimPositions { TRIM_TRAILING = 1 << 1, TRIM_ALL = TRIM_LEADING | TRIM_TRAILING, }; -BASE_EXPORT TrimPositions TrimWhitespace(const string16& input, +BASE_EXPORT TrimPositions TrimWhitespace(const base::string16& input, TrimPositions positions, - string16* output); + base::string16* output); BASE_EXPORT TrimPositions TrimWhitespaceASCII(const std::string& input, TrimPositions positions, std::string* output); @@ -249,11 +232,8 @@ BASE_EXPORT TrimPositions TrimWhitespace(const std::string& input, // (2) If |trim_sequences_with_line_breaks| is true, any other whitespace // sequences containing a CR or LF are trimmed. // (3) All other whitespace sequences are converted to single spaces. -BASE_EXPORT std::wstring CollapseWhitespace( - const std::wstring& text, - bool trim_sequences_with_line_breaks); -BASE_EXPORT string16 CollapseWhitespace( - const string16& text, +BASE_EXPORT base::string16 CollapseWhitespace( + const base::string16& text, bool trim_sequences_with_line_breaks); BASE_EXPORT std::string CollapseWhitespaceASCII( const std::string& text, @@ -262,25 +242,19 @@ BASE_EXPORT std::string CollapseWhitespaceASCII( // Returns true if the passed string is empty or contains only white-space // characters. BASE_EXPORT bool ContainsOnlyWhitespaceASCII(const std::string& str); -BASE_EXPORT bool ContainsOnlyWhitespace(const string16& str); +BASE_EXPORT bool ContainsOnlyWhitespace(const base::string16& str); // Returns true if |input| is empty or contains only characters found in // |characters|. -BASE_EXPORT bool ContainsOnlyChars(const std::wstring& input, - const std::wstring& characters); -BASE_EXPORT bool ContainsOnlyChars(const string16& input, - const string16& characters); +BASE_EXPORT bool ContainsOnlyChars(const base::string16& input, + const base::string16& characters); BASE_EXPORT bool ContainsOnlyChars(const std::string& input, const std::string& characters); // Converts to 7-bit ASCII by truncating. The result must be known to be ASCII // beforehand. BASE_EXPORT std::string WideToASCII(const std::wstring& wide); -BASE_EXPORT std::string UTF16ToASCII(const string16& utf16); - -// Converts the given wide string to the corresponding Latin1. This will fail -// (return false) if any characters are more than 255. -BASE_EXPORT bool WideToLatin1(const std::wstring& wide, std::string* latin1); +BASE_EXPORT std::string UTF16ToASCII(const base::string16& utf16); // Returns true if the specified string matches the criteria. How can a wide // string be 8-bit or UTF8? It contains only characters that are < 256 (in the @@ -294,9 +268,8 @@ BASE_EXPORT bool WideToLatin1(const std::wstring& wide, std::string* latin1); // there's a use case for just checking the structural validity, we have to // add a new function for that. BASE_EXPORT bool IsStringUTF8(const std::string& str); -BASE_EXPORT bool IsStringASCII(const std::wstring& str); BASE_EXPORT bool IsStringASCII(const base::StringPiece& str); -BASE_EXPORT bool IsStringASCII(const string16& str); +BASE_EXPORT bool IsStringASCII(const base::string16& str); // Converts the elements of the given string. This version uses a pointer to // clearly differentiate it from the non-pointer variant. @@ -331,53 +304,40 @@ template <class str> inline str StringToUpperASCII(const str& s) { // token, and it is optimized to avoid intermediate string copies. This API is // borrowed from the equivalent APIs in Mozilla. BASE_EXPORT bool LowerCaseEqualsASCII(const std::string& a, const char* b); -BASE_EXPORT bool LowerCaseEqualsASCII(const std::wstring& a, const char* b); -BASE_EXPORT bool LowerCaseEqualsASCII(const string16& a, const char* b); +BASE_EXPORT bool LowerCaseEqualsASCII(const base::string16& a, const char* b); // Same thing, but with string iterators instead. BASE_EXPORT bool LowerCaseEqualsASCII(std::string::const_iterator a_begin, std::string::const_iterator a_end, const char* b); -BASE_EXPORT bool LowerCaseEqualsASCII(std::wstring::const_iterator a_begin, - std::wstring::const_iterator a_end, - const char* b); -BASE_EXPORT bool LowerCaseEqualsASCII(string16::const_iterator a_begin, - string16::const_iterator a_end, +BASE_EXPORT bool LowerCaseEqualsASCII(base::string16::const_iterator a_begin, + base::string16::const_iterator a_end, const char* b); BASE_EXPORT bool LowerCaseEqualsASCII(const char* a_begin, const char* a_end, const char* b); -BASE_EXPORT bool LowerCaseEqualsASCII(const wchar_t* a_begin, - const wchar_t* a_end, - const char* b); -BASE_EXPORT bool LowerCaseEqualsASCII(const char16* a_begin, - const char16* a_end, +BASE_EXPORT bool LowerCaseEqualsASCII(const base::char16* a_begin, + const base::char16* a_end, const char* b); // Performs a case-sensitive string compare. The behavior is undefined if both // strings are not ASCII. -BASE_EXPORT bool EqualsASCII(const string16& a, const base::StringPiece& b); +BASE_EXPORT bool EqualsASCII(const base::string16& a, const base::StringPiece& b); // Returns true if str starts with search, or false otherwise. BASE_EXPORT bool StartsWithASCII(const std::string& str, const std::string& search, bool case_sensitive); -BASE_EXPORT bool StartsWith(const std::wstring& str, - const std::wstring& search, - bool case_sensitive); -BASE_EXPORT bool StartsWith(const string16& str, - const string16& search, +BASE_EXPORT bool StartsWith(const base::string16& str, + const base::string16& search, bool case_sensitive); // Returns true if str ends with search, or false otherwise. BASE_EXPORT bool EndsWith(const std::string& str, const std::string& search, bool case_sensitive); -BASE_EXPORT bool EndsWith(const std::wstring& str, - const std::wstring& search, - bool case_sensitive); -BASE_EXPORT bool EndsWith(const string16& str, - const string16& search, +BASE_EXPORT bool EndsWith(const base::string16& str, + const base::string16& search, bool case_sensitive); @@ -417,22 +377,22 @@ inline Char HexDigitToInt(Char c) { // Returns true if it's a whitespace character. inline bool IsWhitespace(wchar_t c) { - return wcschr(kWhitespaceWide, c) != NULL; + return wcschr(base::kWhitespaceWide, c) != NULL; } // Return a byte string in human-readable format with a unit suffix. Not // appropriate for use in any UI; use of FormatBytes and friends in ui/base is // highly recommended instead. TODO(avi): Figure out how to get callers to use // FormatBytes instead; remove this. -BASE_EXPORT string16 FormatBytesUnlocalized(int64 bytes); +BASE_EXPORT base::string16 FormatBytesUnlocalized(int64 bytes); // Starting at |start_offset| (usually 0), replace the first instance of // |find_this| with |replace_with|. BASE_EXPORT void ReplaceFirstSubstringAfterOffset( - string16* str, - string16::size_type start_offset, - const string16& find_this, - const string16& replace_with); + base::string16* str, + base::string16::size_type start_offset, + const base::string16& find_this, + const base::string16& replace_with); BASE_EXPORT void ReplaceFirstSubstringAfterOffset( std::string* str, std::string::size_type start_offset, @@ -446,10 +406,10 @@ BASE_EXPORT void ReplaceFirstSubstringAfterOffset( // characters, for example: // std::replace(str.begin(), str.end(), 'a', 'b'); BASE_EXPORT void ReplaceSubstringsAfterOffset( - string16* str, - string16::size_type start_offset, - const string16& find_this, - const string16& replace_with); + base::string16* str, + base::string16::size_type start_offset, + const base::string16& find_this, + const base::string16& replace_with); BASE_EXPORT void ReplaceSubstringsAfterOffset( std::string* str, std::string::size_type start_offset, @@ -490,12 +450,9 @@ inline typename string_type::value_type* WriteInto(string_type* str, // Splits a string into its fields delimited by any of the characters in // |delimiters|. Each field is added to the |tokens| vector. Returns the // number of tokens found. -BASE_EXPORT size_t Tokenize(const std::wstring& str, - const std::wstring& delimiters, - std::vector<std::wstring>* tokens); -BASE_EXPORT size_t Tokenize(const string16& str, - const string16& delimiters, - std::vector<string16>* tokens); +BASE_EXPORT size_t Tokenize(const base::string16& str, + const base::string16& delimiters, + std::vector<base::string16>* tokens); BASE_EXPORT size_t Tokenize(const std::string& str, const std::string& delimiters, std::vector<std::string>* tokens); @@ -504,7 +461,8 @@ BASE_EXPORT size_t Tokenize(const base::StringPiece& str, std::vector<base::StringPiece>* tokens); // Does the opposite of SplitString(). -BASE_EXPORT string16 JoinString(const std::vector<string16>& parts, char16 s); +BASE_EXPORT base::string16 JoinString(const std::vector<base::string16>& parts, + base::char16 s); BASE_EXPORT std::string JoinString( const std::vector<std::string>& parts, char s); @@ -512,17 +470,17 @@ BASE_EXPORT std::string JoinString( BASE_EXPORT std::string JoinString( const std::vector<std::string>& parts, const std::string& separator); -BASE_EXPORT string16 JoinString( - const std::vector<string16>& parts, - const string16& separator); +BASE_EXPORT base::string16 JoinString( + const std::vector<base::string16>& parts, + const base::string16& separator); // Replace $1-$2-$3..$9 in the format string with |a|-|b|-|c|..|i| respectively. // Additionally, any number of consecutive '$' characters is replaced by that // number less one. Eg $$->$, $$$->$$, etc. The offsets parameter here can be // NULL. This only allows you to use up to nine replacements. -BASE_EXPORT string16 ReplaceStringPlaceholders( - const string16& format_string, - const std::vector<string16>& subst, +BASE_EXPORT base::string16 ReplaceStringPlaceholders( + const base::string16& format_string, + const std::vector<base::string16>& subst, std::vector<size_t>* offsets); BASE_EXPORT std::string ReplaceStringPlaceholders( @@ -531,9 +489,10 @@ BASE_EXPORT std::string ReplaceStringPlaceholders( std::vector<size_t>* offsets); // Single-string shortcut for ReplaceStringHolders. |offset| may be NULL. -BASE_EXPORT string16 ReplaceStringPlaceholders(const string16& format_string, - const string16& a, - size_t* offset); +BASE_EXPORT base::string16 ReplaceStringPlaceholders( + const base::string16& format_string, + const base::string16& a, + size_t* offset); // Returns true if the string passed in matches the pattern. The pattern // string can contain wildcards like * and ? @@ -542,7 +501,8 @@ BASE_EXPORT string16 ReplaceStringPlaceholders(const string16& format_string, // ? matches 0 or 1 character, while * matches 0 or more characters. BASE_EXPORT bool MatchPattern(const base::StringPiece& string, const base::StringPiece& pattern); -BASE_EXPORT bool MatchPattern(const string16& string, const string16& pattern); +BASE_EXPORT bool MatchPattern(const base::string16& string, + const base::string16& pattern); // Hack to convert any char-like type to its unsigned counterpart. // For example, it will convert char, signed char and unsigned char to unsigned diff --git a/chromium/base/strings/string_util_constants.cc b/chromium/base/strings/string_util_constants.cc index d92e40cf37b..2a28a2b5126 100644 --- a/chromium/base/strings/string_util_constants.cc +++ b/chromium/base/strings/string_util_constants.cc @@ -4,6 +4,8 @@ #include "base/strings/string_util.h" +namespace base { + #define WHITESPACE_UNICODE \ 0x0009, /* <control-0009> to <control-000D> */ \ 0x000A, \ @@ -53,3 +55,5 @@ const char kWhitespaceASCII[] = { }; const char kUtf8ByteOrderMark[] = "\xEF\xBB\xBF"; + +} // namespace base diff --git a/chromium/base/strings/string_util_unittest.cc b/chromium/base/strings/string_util_unittest.cc index 58b7620b352..e743bb41223 100644 --- a/chromium/base/strings/string_util_unittest.cc +++ b/chromium/base/strings/string_util_unittest.cc @@ -7,8 +7,7 @@ #include <math.h> #include <stdarg.h> -#include <limits> -#include <sstream> +#include <algorithm> #include "base/basictypes.h" #include "base/strings/string16.h" @@ -284,7 +283,8 @@ static const struct collapse_case { TEST(StringUtilTest, CollapseWhitespace) { for (size_t i = 0; i < arraysize(collapse_cases); ++i) { const collapse_case& value = collapse_cases[i]; - EXPECT_EQ(value.output, CollapseWhitespace(value.input, value.trim)); + EXPECT_EQ(WideToUTF16(value.output), + CollapseWhitespace(WideToUTF16(value.input), value.trim)); } } @@ -421,13 +421,11 @@ TEST(StringUtilTest, ConvertASCII) { std::wstring wide = ASCIIToWide(char_cases[i]); EXPECT_EQ(wchar_cases[i], wide); - EXPECT_TRUE(IsStringASCII(wchar_cases[i])); std::string ascii = WideToASCII(wchar_cases[i]); EXPECT_EQ(char_cases[i], ascii); } EXPECT_FALSE(IsStringASCII("Google \x80Video")); - EXPECT_FALSE(IsStringASCII(L"Google \x80Video")); // Convert empty strings. std::wstring wempty; @@ -476,17 +474,16 @@ TEST(StringUtilTest, ToUpperASCII) { TEST(StringUtilTest, LowerCaseEqualsASCII) { static const struct { - const wchar_t* src_w; const char* src_a; const char* dst; } lowercase_cases[] = { - { L"FoO", "FoO", "foo" }, - { L"foo", "foo", "foo" }, - { L"FOO", "FOO", "foo" }, + { "FoO", "foo" }, + { "foo", "foo" }, + { "FOO", "foo" }, }; for (size_t i = 0; i < ARRAYSIZE_UNSAFE(lowercase_cases); ++i) { - EXPECT_TRUE(LowerCaseEqualsASCII(lowercase_cases[i].src_w, + EXPECT_TRUE(LowerCaseEqualsASCII(ASCIIToUTF16(lowercase_cases[i].src_a), lowercase_cases[i].dst)); EXPECT_TRUE(LowerCaseEqualsASCII(lowercase_cases[i].src_a, lowercase_cases[i].dst)); @@ -818,35 +815,48 @@ TEST(StringUtilTest, StartsWith) { EXPECT_TRUE(StartsWithASCII("java", std::string(), false)); EXPECT_TRUE(StartsWithASCII("java", std::string(), true)); - EXPECT_TRUE(StartsWith(L"javascript:url", L"javascript", true)); - EXPECT_FALSE(StartsWith(L"JavaScript:url", L"javascript", true)); - EXPECT_TRUE(StartsWith(L"javascript:url", L"javascript", false)); - EXPECT_TRUE(StartsWith(L"JavaScript:url", L"javascript", false)); - EXPECT_FALSE(StartsWith(L"java", L"javascript", true)); - EXPECT_FALSE(StartsWith(L"java", L"javascript", false)); - EXPECT_FALSE(StartsWith(std::wstring(), L"javascript", false)); - EXPECT_FALSE(StartsWith(std::wstring(), L"javascript", true)); - EXPECT_TRUE(StartsWith(L"java", std::wstring(), false)); - EXPECT_TRUE(StartsWith(L"java", std::wstring(), true)); + EXPECT_TRUE(StartsWith(ASCIIToUTF16("javascript:url"), + ASCIIToUTF16("javascript"), true)); + EXPECT_FALSE(StartsWith(ASCIIToUTF16("JavaScript:url"), + ASCIIToUTF16("javascript"), true)); + EXPECT_TRUE(StartsWith(ASCIIToUTF16("javascript:url"), + ASCIIToUTF16("javascript"), false)); + EXPECT_TRUE(StartsWith(ASCIIToUTF16("JavaScript:url"), + ASCIIToUTF16("javascript"), false)); + EXPECT_FALSE(StartsWith(ASCIIToUTF16("java"), + ASCIIToUTF16("javascript"), true)); + EXPECT_FALSE(StartsWith(ASCIIToUTF16("java"), + ASCIIToUTF16("javascript"), false)); + EXPECT_FALSE(StartsWith(string16(), ASCIIToUTF16("javascript"), false)); + EXPECT_FALSE(StartsWith(string16(), ASCIIToUTF16("javascript"), true)); + EXPECT_TRUE(StartsWith(ASCIIToUTF16("java"), string16(), false)); + EXPECT_TRUE(StartsWith(ASCIIToUTF16("java"), string16(), true)); } TEST(StringUtilTest, EndsWith) { - EXPECT_TRUE(EndsWith(L"Foo.plugin", L".plugin", true)); - EXPECT_FALSE(EndsWith(L"Foo.Plugin", L".plugin", true)); - EXPECT_TRUE(EndsWith(L"Foo.plugin", L".plugin", false)); - EXPECT_TRUE(EndsWith(L"Foo.Plugin", L".plugin", false)); - EXPECT_FALSE(EndsWith(L".plug", L".plugin", true)); - EXPECT_FALSE(EndsWith(L".plug", L".plugin", false)); - EXPECT_FALSE(EndsWith(L"Foo.plugin Bar", L".plugin", true)); - EXPECT_FALSE(EndsWith(L"Foo.plugin Bar", L".plugin", false)); - EXPECT_FALSE(EndsWith(std::wstring(), L".plugin", false)); - EXPECT_FALSE(EndsWith(std::wstring(), L".plugin", true)); - EXPECT_TRUE(EndsWith(L"Foo.plugin", std::wstring(), false)); - EXPECT_TRUE(EndsWith(L"Foo.plugin", std::wstring(), true)); - EXPECT_TRUE(EndsWith(L".plugin", L".plugin", false)); - EXPECT_TRUE(EndsWith(L".plugin", L".plugin", true)); - EXPECT_TRUE(EndsWith(std::wstring(), std::wstring(), false)); - EXPECT_TRUE(EndsWith(std::wstring(), std::wstring(), true)); + EXPECT_TRUE(EndsWith(ASCIIToUTF16("Foo.plugin"), + ASCIIToUTF16(".plugin"), true)); + EXPECT_FALSE(EndsWith(ASCIIToUTF16("Foo.Plugin"), + ASCIIToUTF16(".plugin"), true)); + EXPECT_TRUE(EndsWith(ASCIIToUTF16("Foo.plugin"), + ASCIIToUTF16(".plugin"), false)); + EXPECT_TRUE(EndsWith(ASCIIToUTF16("Foo.Plugin"), + ASCIIToUTF16(".plugin"), false)); + EXPECT_FALSE(EndsWith(ASCIIToUTF16(".plug"), ASCIIToUTF16(".plugin"), true)); + EXPECT_FALSE(EndsWith(ASCIIToUTF16(".plug"), ASCIIToUTF16(".plugin"), false)); + EXPECT_FALSE(EndsWith(ASCIIToUTF16("Foo.plugin Bar"), + ASCIIToUTF16(".plugin"), true)); + EXPECT_FALSE(EndsWith(ASCIIToUTF16("Foo.plugin Bar"), + ASCIIToUTF16(".plugin"), false)); + EXPECT_FALSE(EndsWith(string16(), ASCIIToUTF16(".plugin"), false)); + EXPECT_FALSE(EndsWith(string16(), ASCIIToUTF16(".plugin"), true)); + EXPECT_TRUE(EndsWith(ASCIIToUTF16("Foo.plugin"), string16(), false)); + EXPECT_TRUE(EndsWith(ASCIIToUTF16("Foo.plugin"), string16(), true)); + EXPECT_TRUE(EndsWith(ASCIIToUTF16(".plugin"), + ASCIIToUTF16(".plugin"), false)); + EXPECT_TRUE(EndsWith(ASCIIToUTF16(".plugin"), ASCIIToUTF16(".plugin"), true)); + EXPECT_TRUE(EndsWith(string16(), string16(), false)); + EXPECT_TRUE(EndsWith(string16(), string16(), true)); } TEST(StringUtilTest, GetStringFWithOffsets) { diff --git a/chromium/base/supports_user_data.h b/chromium/base/supports_user_data.h index 77367553daf..6d515d7341e 100644 --- a/chromium/base/supports_user_data.h +++ b/chromium/base/supports_user_data.h @@ -61,7 +61,7 @@ class BASE_EXPORT SupportsUserData { template <typename T> class UserDataAdapter : public base::SupportsUserData::Data { public: - static T* Get(SupportsUserData* supports_user_data, const char* key) { + static T* Get(SupportsUserData* supports_user_data, const void* key) { UserDataAdapter* data = static_cast<UserDataAdapter*>(supports_user_data->GetUserData(key)); return data ? static_cast<T*>(data->object_.get()) : NULL; diff --git a/chromium/base/sync_socket.h b/chromium/base/sync_socket.h index 8ba3f6c2652..71addd1e163 100644 --- a/chromium/base/sync_socket.h +++ b/chromium/base/sync_socket.h @@ -18,6 +18,7 @@ #include "base/base_export.h" #include "base/compiler_specific.h" #include "base/synchronization/waitable_event.h" +#include "base/time/time.h" namespace base { @@ -58,6 +59,13 @@ class BASE_EXPORT SyncSocket { // Returns the number of bytes received, or 0 upon failure. virtual size_t Receive(void* buffer, size_t length); + // Same as Receive() but only blocks for data until |timeout| has elapsed or + // |buffer| |length| is exhausted. Currently only timeouts less than one + // second are allowed. Return the amount of data read. + virtual size_t ReceiveWithTimeout(void* buffer, + size_t length, + TimeDelta timeout); + // Returns the number of bytes available. If non-zero, Receive() will not // not block when called. NOTE: Some implementations cannot reliably // determine the number of bytes available so avoid using the returned @@ -102,6 +110,9 @@ class BASE_EXPORT CancelableSyncSocket : public SyncSocket { // SyncSocket methods in order to support shutting down the 'socket'. virtual bool Close() OVERRIDE; virtual size_t Receive(void* buffer, size_t length) OVERRIDE; + virtual size_t ReceiveWithTimeout(void* buffer, + size_t length, + TimeDelta timeout) OVERRIDE; #endif // Send() is overridden to catch cases where the remote end is not responding diff --git a/chromium/base/sync_socket_nacl.cc b/chromium/base/sync_socket_nacl.cc index 7bab8840c01..f6d17e7ffb6 100644 --- a/chromium/base/sync_socket_nacl.cc +++ b/chromium/base/sync_socket_nacl.cc @@ -11,7 +11,6 @@ #include "base/logging.h" - namespace base { const SyncSocket::Handle SyncSocket::kInvalidHandle = -1; @@ -20,6 +19,7 @@ SyncSocket::SyncSocket() : handle_(kInvalidHandle) { } SyncSocket::~SyncSocket() { + Close(); } // static @@ -31,22 +31,29 @@ bool SyncSocket::Close() { if (handle_ != kInvalidHandle) { if (close(handle_) < 0) DPLOG(ERROR) << "close"; - handle_ = -1; + handle_ = kInvalidHandle; } return true; } size_t SyncSocket::Send(const void* buffer, size_t length) { - // Not implemented since it's not needed by any client code yet. - return -1; + const ssize_t bytes_written = write(handle_, buffer, length); + return bytes_written > 0 ? bytes_written : 0; } size_t SyncSocket::Receive(void* buffer, size_t length) { - return read(handle_, buffer, length); + const ssize_t bytes_read = read(handle_, buffer, length); + return bytes_read > 0 ? bytes_read : 0; +} + +size_t SyncSocket::ReceiveWithTimeout(void* buffer, size_t length, TimeDelta) { + NOTIMPLEMENTED(); + return 0; } size_t SyncSocket::Peek() { - return -1; + NOTIMPLEMENTED(); + return 0; } CancelableSyncSocket::CancelableSyncSocket() { @@ -57,11 +64,11 @@ CancelableSyncSocket::CancelableSyncSocket(Handle handle) } size_t CancelableSyncSocket::Send(const void* buffer, size_t length) { - return -1; + return SyncSocket::Send(buffer, length); } bool CancelableSyncSocket::Shutdown() { - return false; + return SyncSocket::Close(); } // static diff --git a/chromium/base/sync_socket_posix.cc b/chromium/base/sync_socket_posix.cc index 257916df335..5bb893aae87 100644 --- a/chromium/base/sync_socket_posix.cc +++ b/chromium/base/sync_socket_posix.cc @@ -5,8 +5,8 @@ #include "base/sync_socket.h" #include <errno.h> -#include <limits.h> #include <fcntl.h> +#include <limits.h> #include <stdio.h> #include <sys/ioctl.h> #include <sys/socket.h> @@ -18,7 +18,7 @@ #include "base/file_util.h" #include "base/logging.h" - +#include "base/threading/thread_restrictions.h" namespace base { @@ -27,6 +27,28 @@ namespace { // we clamp message lengths, which are size_t, to no more than INT_MAX. const size_t kMaxMessageLength = static_cast<size_t>(INT_MAX); +// Writes |length| of |buffer| into |handle|. Returns the number of bytes +// written or zero on error. |length| must be greater than 0. +size_t SendHelper(SyncSocket::Handle handle, + const void* buffer, + size_t length) { + DCHECK_GT(length, 0u); + DCHECK_LE(length, kMaxMessageLength); + DCHECK_NE(handle, SyncSocket::kInvalidHandle); + const char* charbuffer = static_cast<const char*>(buffer); + const int len = file_util::WriteFileDescriptor(handle, charbuffer, length); + return len < 0 ? 0 : static_cast<size_t>(len); +} + +bool CloseHandle(SyncSocket::Handle handle) { + if (handle != SyncSocket::kInvalidHandle && close(handle) < 0) { + DPLOG(ERROR) << "close"; + return false; + } + + return true; +} + } // namespace const SyncSocket::Handle SyncSocket::kInvalidHandle = -1; @@ -39,17 +61,20 @@ SyncSocket::~SyncSocket() { // static bool SyncSocket::CreatePair(SyncSocket* socket_a, SyncSocket* socket_b) { - DCHECK(socket_a != socket_b); - DCHECK(socket_a->handle_ == kInvalidHandle); - DCHECK(socket_b->handle_ == kInvalidHandle); + DCHECK_NE(socket_a, socket_b); + DCHECK_EQ(socket_a->handle_, kInvalidHandle); + DCHECK_EQ(socket_b->handle_, kInvalidHandle); #if defined(OS_MACOSX) int nosigpipe = 1; #endif // defined(OS_MACOSX) Handle handles[2] = { kInvalidHandle, kInvalidHandle }; - if (socketpair(AF_UNIX, SOCK_STREAM, 0, handles) != 0) - goto cleanup; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, handles) != 0) { + CloseHandle(handles[0]); + CloseHandle(handles[1]); + return false; + } #if defined(OS_MACOSX) // On OSX an attempt to read or write to a closed socket may generate a @@ -58,7 +83,9 @@ bool SyncSocket::CreatePair(SyncSocket* socket_a, SyncSocket* socket_b) { &nosigpipe, sizeof nosigpipe) || 0 != setsockopt(handles[1], SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof nosigpipe)) { - goto cleanup; + CloseHandle(handles[0]); + CloseHandle(handles[1]); + return false; } #endif @@ -67,54 +94,101 @@ bool SyncSocket::CreatePair(SyncSocket* socket_a, SyncSocket* socket_b) { socket_b->handle_ = handles[1]; return true; - - cleanup: - if (handles[0] != kInvalidHandle) { - if (HANDLE_EINTR(close(handles[0])) < 0) - DPLOG(ERROR) << "close"; - } - if (handles[1] != kInvalidHandle) { - if (HANDLE_EINTR(close(handles[1])) < 0) - DPLOG(ERROR) << "close"; - } - - return false; } bool SyncSocket::Close() { - if (handle_ == kInvalidHandle) { - return false; - } - int retval = HANDLE_EINTR(close(handle_)); - if (retval < 0) - DPLOG(ERROR) << "close"; + const bool retval = CloseHandle(handle_); handle_ = kInvalidHandle; - return (retval == 0); + return retval; } size_t SyncSocket::Send(const void* buffer, size_t length) { - DCHECK_LE(length, kMaxMessageLength); - const char* charbuffer = static_cast<const char*>(buffer); - int len = file_util::WriteFileDescriptor(handle_, charbuffer, length); - - return (len == -1) ? 0 : static_cast<size_t>(len); + ThreadRestrictions::AssertIOAllowed(); + return SendHelper(handle_, buffer, length); } size_t SyncSocket::Receive(void* buffer, size_t length) { + ThreadRestrictions::AssertIOAllowed(); + DCHECK_GT(length, 0u); DCHECK_LE(length, kMaxMessageLength); + DCHECK_NE(handle_, kInvalidHandle); char* charbuffer = static_cast<char*>(buffer); - if (file_util::ReadFromFD(handle_, charbuffer, length)) + if (ReadFromFD(handle_, charbuffer, length)) return length; return 0; } +size_t SyncSocket::ReceiveWithTimeout(void* buffer, + size_t length, + TimeDelta timeout) { + ThreadRestrictions::AssertIOAllowed(); + DCHECK_GT(length, 0u); + DCHECK_LE(length, kMaxMessageLength); + DCHECK_NE(handle_, kInvalidHandle); + + // TODO(dalecurtis): There's an undiagnosed issue on OSX where we're seeing + // large numbers of open files which prevents select() from being used. In + // this case, the best we can do is Peek() to see if we can Receive() now or + // return a timeout error (0) if not. See http://crbug.com/314364. + if (handle_ >= FD_SETSIZE) + return Peek() < length ? 0 : Receive(buffer, length); + + // Only timeouts greater than zero and less than one second are allowed. + DCHECK_GT(timeout.InMicroseconds(), 0); + DCHECK_LT(timeout.InMicroseconds(), + base::TimeDelta::FromSeconds(1).InMicroseconds()); + + // Track the start time so we can reduce the timeout as data is read. + TimeTicks start_time = TimeTicks::Now(); + const TimeTicks finish_time = start_time + timeout; + + fd_set read_fds; + size_t bytes_read_total; + for (bytes_read_total = 0; + bytes_read_total < length && timeout.InMicroseconds() > 0; + timeout = finish_time - base::TimeTicks::Now()) { + FD_ZERO(&read_fds); + FD_SET(handle_, &read_fds); + + // Wait for data to become available. + struct timeval timeout_struct = + { 0, static_cast<suseconds_t>(timeout.InMicroseconds()) }; + const int select_result = + select(handle_ + 1, &read_fds, NULL, NULL, &timeout_struct); + // Handle EINTR manually since we need to update the timeout value. + if (select_result == -1 && errno == EINTR) + continue; + if (select_result <= 0) + return bytes_read_total; + + // select() only tells us that data is ready for reading, not how much. We + // must Peek() for the amount ready for reading to avoid blocking. + DCHECK(FD_ISSET(handle_, &read_fds)); + const size_t bytes_to_read = std::min(Peek(), length - bytes_read_total); + + // There may be zero bytes to read if the socket at the other end closed. + if (!bytes_to_read) + return bytes_read_total; + + const size_t bytes_received = + Receive(static_cast<char*>(buffer) + bytes_read_total, bytes_to_read); + bytes_read_total += bytes_received; + if (bytes_received != bytes_to_read) + return bytes_read_total; + } + + return bytes_read_total; +} + size_t SyncSocket::Peek() { - int number_chars; - if (-1 == ioctl(handle_, FIONREAD, &number_chars)) { + DCHECK_NE(handle_, kInvalidHandle); + int number_chars = 0; + if (ioctl(handle_, FIONREAD, &number_chars) == -1) { // If there is an error in ioctl, signal that the channel would block. return 0; } - return (size_t) number_chars; + DCHECK_GE(number_chars, 0); + return number_chars; } CancelableSyncSocket::CancelableSyncSocket() {} @@ -123,19 +197,23 @@ CancelableSyncSocket::CancelableSyncSocket(Handle handle) } bool CancelableSyncSocket::Shutdown() { - return HANDLE_EINTR(shutdown(handle(), SHUT_RDWR)) >= 0; + DCHECK_NE(handle_, kInvalidHandle); + return HANDLE_EINTR(shutdown(handle_, SHUT_RDWR)) >= 0; } size_t CancelableSyncSocket::Send(const void* buffer, size_t length) { - long flags = 0; - flags = fcntl(handle_, F_GETFL, NULL); + DCHECK_GT(length, 0u); + DCHECK_LE(length, kMaxMessageLength); + DCHECK_NE(handle_, kInvalidHandle); + + const long flags = fcntl(handle_, F_GETFL, NULL); if (flags != -1 && (flags & O_NONBLOCK) == 0) { // Set the socket to non-blocking mode for sending if its original mode // is blocking. fcntl(handle_, F_SETFL, flags | O_NONBLOCK); } - size_t len = SyncSocket::Send(buffer, length); + const size_t len = SendHelper(handle_, buffer, length); if (flags != -1 && (flags & O_NONBLOCK) == 0) { // Restore the original flags. diff --git a/chromium/base/sync_socket_unittest.cc b/chromium/base/sync_socket_unittest.cc new file mode 100644 index 00000000000..7e4089cb53c --- /dev/null +++ b/chromium/base/sync_socket_unittest.cc @@ -0,0 +1,131 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/basictypes.h" +#include "base/sync_socket.h" +#include "base/threading/simple_thread.h" +#include "base/time/time.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +const int kReceiveTimeoutInMilliseconds = 750; + +class HangingReceiveThread : public base::DelegateSimpleThread::Delegate { + public: + explicit HangingReceiveThread(base::SyncSocket* socket) + : socket_(socket), + thread_(this, "HangingReceiveThread") { + thread_.Start(); + } + + virtual ~HangingReceiveThread() {} + + virtual void Run() OVERRIDE { + int data = 0; + ASSERT_EQ(socket_->Peek(), 0u); + + // Use receive with timeout so we don't hang the test harness indefinitely. + ASSERT_EQ(0u, socket_->ReceiveWithTimeout( + &data, sizeof(data), base::TimeDelta::FromMilliseconds( + kReceiveTimeoutInMilliseconds))); + } + + void Stop() { + thread_.Join(); + } + + private: + base::SyncSocket* socket_; + base::DelegateSimpleThread thread_; + + DISALLOW_COPY_AND_ASSIGN(HangingReceiveThread); +}; + +// Tests sending data between two SyncSockets. Uses ASSERT() and thus will exit +// early upon failure. Callers should use ASSERT_NO_FATAL_FAILURE() if testing +// continues after return. +void SendReceivePeek(base::SyncSocket* socket_a, base::SyncSocket* socket_b) { + int received = 0; + const int kSending = 123; + COMPILE_ASSERT(sizeof(kSending) == sizeof(received), Invalid_Data_Size); + + ASSERT_EQ(0u, socket_a->Peek()); + ASSERT_EQ(0u, socket_b->Peek()); + + // Verify |socket_a| can send to |socket_a| and |socket_a| can Receive from + // |socket_a|. + ASSERT_EQ(sizeof(kSending), socket_a->Send(&kSending, sizeof(kSending))); + ASSERT_EQ(sizeof(kSending), socket_b->Peek()); + ASSERT_EQ(sizeof(kSending), socket_b->Receive(&received, sizeof(kSending))); + ASSERT_EQ(kSending, received); + + ASSERT_EQ(0u, socket_a->Peek()); + ASSERT_EQ(0u, socket_b->Peek()); + + // Now verify the reverse. + received = 0; + ASSERT_EQ(sizeof(kSending), socket_b->Send(&kSending, sizeof(kSending))); + ASSERT_EQ(sizeof(kSending), socket_a->Peek()); + ASSERT_EQ(sizeof(kSending), socket_a->Receive(&received, sizeof(kSending))); + ASSERT_EQ(kSending, received); + + ASSERT_EQ(0u, socket_a->Peek()); + ASSERT_EQ(0u, socket_b->Peek()); + + ASSERT_TRUE(socket_a->Close()); + ASSERT_TRUE(socket_b->Close()); +} + +template <class SocketType> +void NormalSendReceivePeek() { + SocketType socket_a, socket_b; + ASSERT_TRUE(SocketType::CreatePair(&socket_a, &socket_b)); + SendReceivePeek(&socket_a, &socket_b); +} + +template <class SocketType> +void ClonedSendReceivePeek() { + SocketType socket_a, socket_b; + ASSERT_TRUE(SocketType::CreatePair(&socket_a, &socket_b)); + + // Create new SyncSockets from the paired handles. + SocketType socket_c(socket_a.handle()), socket_d(socket_b.handle()); + SendReceivePeek(&socket_c, &socket_d); +} + +} // namespace + +TEST(SyncSocket, NormalSendReceivePeek) { + NormalSendReceivePeek<base::SyncSocket>(); +} + +TEST(SyncSocket, ClonedSendReceivePeek) { + ClonedSendReceivePeek<base::SyncSocket>(); +} + +TEST(CancelableSyncSocket, NormalSendReceivePeek) { + NormalSendReceivePeek<base::CancelableSyncSocket>(); +} + +TEST(CancelableSyncSocket, ClonedSendReceivePeek) { + ClonedSendReceivePeek<base::CancelableSyncSocket>(); +} + +TEST(CancelableSyncSocket, CancelReceiveShutdown) { + base::CancelableSyncSocket socket_a, socket_b; + ASSERT_TRUE(base::CancelableSyncSocket::CreatePair(&socket_a, &socket_b)); + + base::TimeTicks start = base::TimeTicks::Now(); + HangingReceiveThread thread(&socket_b); + ASSERT_TRUE(socket_b.Shutdown()); + thread.Stop(); + + // Ensure the receive didn't just timeout. + ASSERT_LT((base::TimeTicks::Now() - start).InMilliseconds(), + kReceiveTimeoutInMilliseconds); + + ASSERT_TRUE(socket_a.Close()); + ASSERT_TRUE(socket_b.Close()); +} diff --git a/chromium/base/sync_socket_win.cc b/chromium/base/sync_socket_win.cc index 99a6afea3ec..26e76ec2738 100644 --- a/chromium/base/sync_socket_win.cc +++ b/chromium/base/sync_socket_win.cc @@ -5,6 +5,7 @@ #include "base/sync_socket.h" #include "base/logging.h" +#include "base/threading/thread_restrictions.h" #include "base/win/scoped_handle.h" namespace base { @@ -27,9 +28,9 @@ const int kInBufferSize = 4096; const int kDefaultTimeoutMilliSeconds = 1000; bool CreatePairImpl(HANDLE* socket_a, HANDLE* socket_b, bool overlapped) { - DCHECK(socket_a != socket_b); - DCHECK(*socket_a == SyncSocket::kInvalidHandle); - DCHECK(*socket_b == SyncSocket::kInvalidHandle); + DCHECK_NE(socket_a, socket_b); + DCHECK_EQ(*socket_a, SyncSocket::kInvalidHandle); + DCHECK_EQ(*socket_b, SyncSocket::kInvalidHandle); wchar_t name[kPipePathMax]; ScopedHandle handle_a; @@ -110,35 +111,53 @@ DWORD GetNextChunkSize(size_t current_pos, size_t max_size) { // on an event that can be used to cancel the operation. If the operation // is cancelled, the function returns and closes the relevant socket object. template <typename BufferType, typename Function> -size_t CancelableFileOperation(Function operation, HANDLE file, - BufferType* buffer, size_t length, - base::WaitableEvent* io_event, - base::WaitableEvent* cancel_event, +size_t CancelableFileOperation(Function operation, + HANDLE file, + BufferType* buffer, + size_t length, + WaitableEvent* io_event, + WaitableEvent* cancel_event, CancelableSyncSocket* socket, DWORD timeout_in_ms) { + ThreadRestrictions::AssertIOAllowed(); // The buffer must be byte size or the length check won't make much sense. COMPILE_ASSERT(sizeof(buffer[0]) == sizeof(char), incorrect_buffer_type); + DCHECK_GT(length, 0u); DCHECK_LE(length, kMaxMessageLength); + DCHECK_NE(file, SyncSocket::kInvalidHandle); + + // Track the finish time so we can calculate the timeout as data is read. + TimeTicks current_time, finish_time; + if (timeout_in_ms != INFINITE) { + current_time = TimeTicks::Now(); + finish_time = + current_time + base::TimeDelta::FromMilliseconds(timeout_in_ms); + } - OVERLAPPED ol = {0}; - ol.hEvent = io_event->handle(); size_t count = 0; - while (count < length) { - DWORD chunk = GetNextChunkSize(count, length); + do { + // The OVERLAPPED structure will be modified by ReadFile or WriteFile. + OVERLAPPED ol = { 0 }; + ol.hEvent = io_event->handle(); + + const DWORD chunk = GetNextChunkSize(count, length); // This is either the ReadFile or WriteFile call depending on whether // we're receiving or sending data. DWORD len = 0; - BOOL ok = operation(file, static_cast<BufferType*>(buffer) + count, chunk, - &len, &ol); - if (!ok) { + const BOOL operation_ok = operation( + file, static_cast<BufferType*>(buffer) + count, chunk, &len, &ol); + if (!operation_ok) { if (::GetLastError() == ERROR_IO_PENDING) { HANDLE events[] = { io_event->handle(), cancel_event->handle() }; - int wait_result = WaitForMultipleObjects( - arraysize(events), events, FALSE, timeout_in_ms); + const int wait_result = WaitForMultipleObjects( + ARRAYSIZE_UNSAFE(events), events, FALSE, + timeout_in_ms == INFINITE + ? timeout_in_ms + : (finish_time - current_time).InMilliseconds()); if (wait_result == (WAIT_OBJECT_0 + 0)) { GetOverlappedResult(file, &ol, &len, TRUE); } else if (wait_result == (WAIT_OBJECT_0 + 1)) { - VLOG(1) << "Shutdown was signaled. Closing socket."; + DVLOG(1) << "Shutdown was signaled. Closing socket."; CancelIo(file); socket->Close(); count = 0; @@ -146,9 +165,8 @@ size_t CancelableFileOperation(Function operation, HANDLE file, } else { // Timeout happened. DCHECK_EQ(WAIT_TIMEOUT, wait_result); - if (!CancelIo(file)){ + if (!CancelIo(file)) DLOG(WARNING) << "CancelIo() failed"; - } break; } } else { @@ -161,9 +179,15 @@ size_t CancelableFileOperation(Function operation, HANDLE file, // Quit the operation if we can't write/read anymore. if (len != chunk) break; - } - return (count > 0) ? count : 0; + // Since TimeTicks::Now() is expensive, only bother updating the time if we + // have more work to do. + if (timeout_in_ms != INFINITE && count < length) + current_time = base::TimeTicks::Now(); + } while (count < length && + (timeout_in_ms == INFINITE || current_time < finish_time)); + + return count; } } // namespace @@ -185,37 +209,50 @@ bool SyncSocket::CreatePair(SyncSocket* socket_a, SyncSocket* socket_b) { bool SyncSocket::Close() { if (handle_ == kInvalidHandle) - return false; + return true; - BOOL retval = CloseHandle(handle_); + const BOOL result = CloseHandle(handle_); handle_ = kInvalidHandle; - return retval ? true : false; + return result == TRUE; } size_t SyncSocket::Send(const void* buffer, size_t length) { + ThreadRestrictions::AssertIOAllowed(); + DCHECK_GT(length, 0u); DCHECK_LE(length, kMaxMessageLength); + DCHECK_NE(handle_, kInvalidHandle); size_t count = 0; while (count < length) { DWORD len; DWORD chunk = GetNextChunkSize(count, length); if (WriteFile(handle_, static_cast<const char*>(buffer) + count, chunk, &len, NULL) == FALSE) { - return (0 < count) ? count : 0; + return count; } count += len; } return count; } +size_t SyncSocket::ReceiveWithTimeout(void* buffer, + size_t length, + TimeDelta timeout) { + NOTIMPLEMENTED(); + return 0; +} + size_t SyncSocket::Receive(void* buffer, size_t length) { + ThreadRestrictions::AssertIOAllowed(); + DCHECK_GT(length, 0u); DCHECK_LE(length, kMaxMessageLength); + DCHECK_NE(handle_, kInvalidHandle); size_t count = 0; while (count < length) { DWORD len; DWORD chunk = GetNextChunkSize(count, length); if (ReadFile(handle_, static_cast<char*>(buffer) + count, chunk, &len, NULL) == FALSE) { - return (0 < count) ? count : 0; + return count; } count += len; } @@ -245,9 +282,9 @@ bool CancelableSyncSocket::Shutdown() { } bool CancelableSyncSocket::Close() { - bool ret = SyncSocket::Close(); + const bool result = SyncSocket::Close(); shutdown_event_.Reset(); - return ret; + return result; } size_t CancelableSyncSocket::Send(const void* buffer, size_t length) { @@ -258,9 +295,17 @@ size_t CancelableSyncSocket::Send(const void* buffer, size_t length) { } size_t CancelableSyncSocket::Receive(void* buffer, size_t length) { - return CancelableFileOperation(&ReadFile, handle_, - reinterpret_cast<char*>(buffer), length, &file_operation_, - &shutdown_event_, this, INFINITE); + return CancelableFileOperation( + &ReadFile, handle_, reinterpret_cast<char*>(buffer), length, + &file_operation_, &shutdown_event_, this, INFINITE); +} + +size_t CancelableSyncSocket::ReceiveWithTimeout(void* buffer, + size_t length, + TimeDelta timeout) { + return CancelableFileOperation( + &ReadFile, handle_, reinterpret_cast<char*>(buffer), length, + &file_operation_, &shutdown_event_, this, timeout.InMilliseconds()); } // static @@ -269,5 +314,4 @@ bool CancelableSyncSocket::CreatePair(CancelableSyncSocket* socket_a, return CreatePairImpl(&socket_a->handle_, &socket_b->handle_, true); } - } // namespace base diff --git a/chromium/base/synchronization/condition_variable_posix.cc b/chromium/base/synchronization/condition_variable_posix.cc index 92b479ede47..e70a301cb2a 100644 --- a/chromium/base/synchronization/condition_variable_posix.cc +++ b/chromium/base/synchronization/condition_variable_posix.cc @@ -20,7 +20,22 @@ ConditionVariable::ConditionVariable(Lock* user_lock) , user_lock_(user_lock) #endif { - int rv = pthread_cond_init(&condition_, NULL); + int rv = 0; + // http://crbug.com/293736 + // NaCl doesn't support monotonic clock based absolute deadlines. + // Android supports it through the non-standard + // pthread_cond_timedwait_monotonic_np. + // Mac can use relative time deadlines. +#if !defined(OS_MACOSX) && !defined(OS_NACL) && !defined(OS_ANDROID) + pthread_condattr_t attrs; + rv = pthread_condattr_init(&attrs); + DCHECK_EQ(0, rv); + pthread_condattr_setclock(&attrs, CLOCK_MONOTONIC); + rv = pthread_cond_init(&condition_, &attrs); + pthread_condattr_destroy(&attrs); +#else + rv = pthread_cond_init(&condition_, NULL); +#endif DCHECK_EQ(0, rv); } @@ -44,23 +59,48 @@ void ConditionVariable::Wait() { void ConditionVariable::TimedWait(const TimeDelta& max_time) { base::ThreadRestrictions::AssertWaitAllowed(); int64 usecs = max_time.InMicroseconds(); + struct timespec relative_time; + relative_time.tv_sec = usecs / Time::kMicrosecondsPerSecond; + relative_time.tv_nsec = + (usecs % Time::kMicrosecondsPerSecond) * Time::kNanosecondsPerMicrosecond; + +#if !defined(NDEBUG) + user_lock_->CheckHeldAndUnmark(); +#endif +#if defined(OS_MACOSX) + int rv = pthread_cond_timedwait_relative_np( + &condition_, user_mutex_, &relative_time); +#else // The timeout argument to pthread_cond_timedwait is in absolute time. + struct timespec absolute_time; +#if defined(OS_NACL) + // See comment in constructor for why this is different in NaCl. struct timeval now; gettimeofday(&now, NULL); + absolute_time.tv_sec = now.tv_sec; + absolute_time.tv_nsec = now.tv_usec * Time::kNanosecondsPerMicrosecond; +#else + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + absolute_time.tv_sec = now.tv_sec; + absolute_time.tv_nsec = now.tv_nsec; +#endif - struct timespec abstime; - abstime.tv_sec = now.tv_sec + (usecs / Time::kMicrosecondsPerSecond); - abstime.tv_nsec = (now.tv_usec + (usecs % Time::kMicrosecondsPerSecond)) * - Time::kNanosecondsPerMicrosecond; - abstime.tv_sec += abstime.tv_nsec / Time::kNanosecondsPerSecond; - abstime.tv_nsec %= Time::kNanosecondsPerSecond; - DCHECK_GE(abstime.tv_sec, now.tv_sec); // Overflow paranoia + absolute_time.tv_sec += relative_time.tv_sec; + absolute_time.tv_nsec += relative_time.tv_nsec; + absolute_time.tv_sec += absolute_time.tv_nsec / Time::kNanosecondsPerSecond; + absolute_time.tv_nsec %= Time::kNanosecondsPerSecond; + DCHECK_GE(absolute_time.tv_sec, now.tv_sec); // Overflow paranoia + +#if defined(OS_ANDROID) + int rv = pthread_cond_timedwait_monotonic_np( + &condition_, user_mutex_, &absolute_time); +#else + int rv = pthread_cond_timedwait(&condition_, user_mutex_, &absolute_time); +#endif // OS_ANDROID +#endif // OS_MACOSX -#if !defined(NDEBUG) - user_lock_->CheckHeldAndUnmark(); -#endif - int rv = pthread_cond_timedwait(&condition_, user_mutex_, &abstime); DCHECK(rv == 0 || rv == ETIMEDOUT); #if !defined(NDEBUG) user_lock_->CheckUnheldAndMark(); diff --git a/chromium/base/synchronization/condition_variable_unittest.cc b/chromium/base/synchronization/condition_variable_unittest.cc index 4230e635495..ee554ff329f 100644 --- a/chromium/base/synchronization/condition_variable_unittest.cc +++ b/chromium/base/synchronization/condition_variable_unittest.cc @@ -8,12 +8,14 @@ #include <algorithm> #include <vector> +#include "base/bind.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/synchronization/condition_variable.h" #include "base/synchronization/lock.h" #include "base/synchronization/spin_wait.h" #include "base/threading/platform_thread.h" +#include "base/threading/thread.h" #include "base/threading/thread_collision_warner.h" #include "base/time/time.h" #include "testing/gtest/include/gtest/gtest.h" @@ -188,9 +190,60 @@ TEST_F(ConditionVariableTest, TimeoutTest) { lock.Release(); } +#if defined(OS_POSIX) +const int kDiscontinuitySeconds = 2; + +void BackInTime(Lock* lock) { + AutoLock auto_lock(*lock); + + timeval tv; + gettimeofday(&tv, NULL); + tv.tv_sec -= kDiscontinuitySeconds; + settimeofday(&tv, NULL); +} + +// Tests that TimedWait ignores changes to the system clock. +// Test is disabled by default, because it needs to run as root to muck with the +// system clock. +// http://crbug.com/293736 +TEST_F(ConditionVariableTest, DISABLED_TimeoutAcrossSetTimeOfDay) { + timeval tv; + gettimeofday(&tv, NULL); + tv.tv_sec += kDiscontinuitySeconds; + if (settimeofday(&tv, NULL) < 0) { + PLOG(ERROR) << "Could not set time of day. Run as root?"; + return; + } + + Lock lock; + ConditionVariable cv(&lock); + lock.Acquire(); + + Thread thread("Helper"); + thread.Start(); + thread.message_loop()->PostTask(FROM_HERE, base::Bind(&BackInTime, &lock)); + + TimeTicks start = TimeTicks::Now(); + const TimeDelta kWaitTime = TimeDelta::FromMilliseconds(300); + // Allow for clocking rate granularity. + const TimeDelta kFudgeTime = TimeDelta::FromMilliseconds(50); + + cv.TimedWait(kWaitTime + kFudgeTime); + TimeDelta duration = TimeTicks::Now() - start; + + thread.Stop(); + // We can't use EXPECT_GE here as the TimeDelta class does not support the + // required stream conversion. + EXPECT_TRUE(duration >= kWaitTime); + EXPECT_TRUE(duration <= TimeDelta::FromSeconds(kDiscontinuitySeconds)); + + lock.Release(); +} +#endif + // Suddenly got flaky on Win, see http://crbug.com/10607 (starting at -// comment #15) +// comment #15). #if defined(OS_WIN) #define MAYBE_MultiThreadConsumerTest DISABLED_MultiThreadConsumerTest #else diff --git a/chromium/base/synchronization/waitable_event_posix.cc b/chromium/base/synchronization/waitable_event_posix.cc index 714c111e197..fccba9d31c6 100644 --- a/chromium/base/synchronization/waitable_event_posix.cc +++ b/chromium/base/synchronization/waitable_event_posix.cc @@ -159,7 +159,7 @@ void WaitableEvent::Wait() { bool WaitableEvent::TimedWait(const TimeDelta& max_time) { base::ThreadRestrictions::AssertWaitAllowed(); - const Time end_time(Time::Now() + max_time); + const TimeTicks end_time(TimeTicks::Now() + max_time); const bool finite_time = max_time.ToInternalValue() >= 0; kernel_->lock_.Acquire(); @@ -184,7 +184,7 @@ bool WaitableEvent::TimedWait(const TimeDelta& max_time) { // again before unlocking it. for (;;) { - const Time current_time(Time::Now()); + const TimeTicks current_time(TimeTicks::Now()); if (sw.fired() || (finite_time && current_time >= end_time)) { const bool return_value = sw.fired(); diff --git a/chromium/base/sys_info.h b/chromium/base/sys_info.h index 38462ed1110..aa40cadfbc9 100644 --- a/chromium/base/sys_info.h +++ b/chromium/base/sys_info.h @@ -5,11 +5,13 @@ #ifndef BASE_SYS_INFO_H_ #define BASE_SYS_INFO_H_ +#include <map> #include <string> #include "base/base_export.h" #include "base/basictypes.h" #include "base/files/file_path.h" +#include "base/time/time.h" #include "build/build_config.h" namespace base { @@ -73,24 +75,34 @@ class BASE_EXPORT SysInfo { static size_t VMAllocationGranularity(); #if defined(OS_POSIX) && !defined(OS_MACOSX) - // Returns the maximum SysV shared memory segment size. + // Returns the maximum SysV shared memory segment size, or zero if there is no + // limit. static size_t MaxSharedMemorySize(); #endif // defined(OS_POSIX) && !defined(OS_MACOSX) #if defined(OS_CHROMEOS) - // Returns the name of the version entry we wish to look up in the - // Linux Standard Base release information file. - static std::string GetLinuxStandardBaseVersionKey(); - - // Parses /etc/lsb-release to get version information for Google Chrome OS. - // Declared here so it can be exposed for unit testing. - static void ParseLsbRelease(const std::string& lsb_release, - int32* major_version, - int32* minor_version, - int32* bugfix_version); - - // Returns the path to the lsb-release file. - static FilePath GetLsbReleaseFilePath(); + typedef std::map<std::string, std::string> LsbReleaseMap; + + // Returns the contents of /etc/lsb-release as a map. + static const LsbReleaseMap& GetLsbReleaseMap(); + + // If |key| is present in the LsbReleaseMap, sets |value| and returns true. + static bool GetLsbReleaseValue(const std::string& key, std::string* value); + + // Convenience function for GetLsbReleaseValue("CHROMEOS_RELEASE_BOARD",...). + // Returns "unknown" if CHROMEOS_RELEASE_BOARD is not set. + static std::string GetLsbReleaseBoard(); + + // Returns the creation time of /etc/lsb-release. (Used to get the date and + // time of the Chrome OS build). + static Time GetLsbReleaseTime(); + + // Returns true when actually running in a Chrome OS environment. + static bool IsRunningOnChromeOS(); + + // Test method to force re-parsing of lsb-release. + static void SetChromeOSVersionInfoForTest(const std::string& lsb_release, + const Time& lsb_release_time); #endif // defined(OS_CHROMEOS) #if defined(OS_ANDROID) diff --git a/chromium/base/sys_info_chromeos.cc b/chromium/base/sys_info_chromeos.cc index 4c1a7af56cb..7cf69753460 100644 --- a/chromium/base/sys_info_chromeos.cc +++ b/chromium/base/sys_info_chromeos.cc @@ -5,115 +5,214 @@ #include "base/sys_info.h" #include "base/basictypes.h" +#include "base/environment.h" #include "base/file_util.h" #include "base/files/file_path.h" #include "base/lazy_instance.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_piece.h" +#include "base/strings/string_split.h" #include "base/strings/string_tokenizer.h" +#include "base/strings/string_util.h" #include "base/threading/thread_restrictions.h" namespace base { -static const char* kLinuxStandardBaseVersionKeys[] = { +namespace { + +const char* kLinuxStandardBaseVersionKeys[] = { "CHROMEOS_RELEASE_VERSION", "GOOGLE_RELEASE", "DISTRIB_RELEASE", - NULL +}; + +const char kChromeOsReleaseNameKey[] = "CHROMEOS_RELEASE_NAME"; + +const char* const kChromeOsReleaseNames[] = { + "Chrome OS", + "Chromium OS", }; const char kLinuxStandardBaseReleaseFile[] = "/etc/lsb-release"; -struct ChromeOSVersionNumbers { - ChromeOSVersionNumbers() - : major_version(0), - minor_version(0), - bugfix_version(0), - parsed(false) { +const char kLsbReleaseKey[] = "LSB_RELEASE"; +const char kLsbReleaseTimeKey[] = "LSB_RELEASE_TIME"; // Seconds since epoch + +const char kLsbReleaseSourceKey[] = "lsb-release"; +const char kLsbReleaseSourceEnv[] = "env"; +const char kLsbReleaseSourceFile[] = "file"; + +class ChromeOSVersionInfo { + public: + ChromeOSVersionInfo() { + Parse(); + } + + void Parse() { + lsb_release_map_.clear(); + major_version_ = 0; + minor_version_ = 0; + bugfix_version_ = 0; + is_running_on_chromeos_ = false; + + std::string lsb_release, lsb_release_time_str; + scoped_ptr<Environment> env(Environment::Create()); + bool parsed_from_env = + env->GetVar(kLsbReleaseKey, &lsb_release) && + env->GetVar(kLsbReleaseTimeKey, &lsb_release_time_str); + if (parsed_from_env) { + double us = 0; + if (StringToDouble(lsb_release_time_str, &us)) + lsb_release_time_ = Time::FromDoubleT(us); + } else { + // If the LSB_RELEASE and LSB_RELEASE_TIME environment variables are not + // set, fall back to a blocking read of the lsb_release file. This should + // only happen in non Chrome OS environments. + ThreadRestrictions::ScopedAllowIO allow_io; + FilePath path(kLinuxStandardBaseReleaseFile); + ReadFileToString(path, &lsb_release); + PlatformFileInfo fileinfo; + if (GetFileInfo(path, &fileinfo)) + lsb_release_time_ = fileinfo.creation_time; + } + ParseLsbRelease(lsb_release); + // For debugging: + lsb_release_map_[kLsbReleaseSourceKey] = + parsed_from_env ? kLsbReleaseSourceEnv : kLsbReleaseSourceFile; + } + + bool GetLsbReleaseValue(const std::string& key, std::string* value) { + SysInfo::LsbReleaseMap::const_iterator iter = lsb_release_map_.find(key); + if (iter == lsb_release_map_.end()) + return false; + *value = iter->second; + return true; + } + + void GetVersionNumbers(int32* major_version, + int32* minor_version, + int32* bugfix_version) { + *major_version = major_version_; + *minor_version = minor_version_; + *bugfix_version = bugfix_version_; } - int32 major_version; - int32 minor_version; - int32 bugfix_version; - bool parsed; + const Time& lsb_release_time() const { return lsb_release_time_; } + const SysInfo::LsbReleaseMap& lsb_release_map() const { + return lsb_release_map_; + } + bool is_running_on_chromeos() const { return is_running_on_chromeos_; } + + private: + void ParseLsbRelease(const std::string& lsb_release) { + // Parse and cache lsb_release key pairs. There should only be a handful + // of entries so the overhead for this will be small, and it can be + // useful for debugging. + std::vector<std::pair<std::string, std::string> > pairs; + SplitStringIntoKeyValuePairs(lsb_release, '=', '\n', &pairs); + for (size_t i = 0; i < pairs.size(); ++i) { + std::string key, value; + TrimWhitespaceASCII(pairs[i].first, TRIM_ALL, &key); + TrimWhitespaceASCII(pairs[i].second, TRIM_ALL, &value); + if (key.empty()) + continue; + lsb_release_map_[key] = value; + } + // Parse the version from the first matching recognized version key. + std::string version; + for (size_t i = 0; i < arraysize(kLinuxStandardBaseVersionKeys); ++i) { + std::string key = kLinuxStandardBaseVersionKeys[i]; + if (GetLsbReleaseValue(key, &version) && !version.empty()) + break; + } + StringTokenizer tokenizer(version, "."); + if (tokenizer.GetNext()) { + StringToInt(StringPiece(tokenizer.token_begin(), tokenizer.token_end()), + &major_version_); + } + if (tokenizer.GetNext()) { + StringToInt(StringPiece(tokenizer.token_begin(), tokenizer.token_end()), + &minor_version_); + } + if (tokenizer.GetNext()) { + StringToInt(StringPiece(tokenizer.token_begin(), tokenizer.token_end()), + &bugfix_version_); + } + + // Check release name for Chrome OS. + std::string release_name; + if (GetLsbReleaseValue(kChromeOsReleaseNameKey, &release_name)) { + for (size_t i = 0; i < arraysize(kChromeOsReleaseNames); ++i) { + if (release_name == kChromeOsReleaseNames[i]) { + is_running_on_chromeos_ = true; + break; + } + } + } + } + + Time lsb_release_time_; + SysInfo::LsbReleaseMap lsb_release_map_; + int32 major_version_; + int32 minor_version_; + int32 bugfix_version_; + bool is_running_on_chromeos_; }; -static LazyInstance<ChromeOSVersionNumbers> - g_chrome_os_version_numbers = LAZY_INSTANCE_INITIALIZER; +static LazyInstance<ChromeOSVersionInfo> + g_chrome_os_version_info = LAZY_INSTANCE_INITIALIZER; + +ChromeOSVersionInfo& GetChromeOSVersionInfo() { + return g_chrome_os_version_info.Get(); +} + +} // namespace // static void SysInfo::OperatingSystemVersionNumbers(int32* major_version, int32* minor_version, int32* bugfix_version) { - if (!g_chrome_os_version_numbers.Get().parsed) { - // The other implementations of SysInfo don't block on the disk. - // See http://code.google.com/p/chromium/issues/detail?id=60394 - // Perhaps the caller ought to cache this? - // Temporary allowing while we work the bug out. - ThreadRestrictions::ScopedAllowIO allow_io; - - FilePath path(kLinuxStandardBaseReleaseFile); - std::string contents; - if (ReadFileToString(path, &contents)) { - g_chrome_os_version_numbers.Get().parsed = true; - ParseLsbRelease(contents, - &(g_chrome_os_version_numbers.Get().major_version), - &(g_chrome_os_version_numbers.Get().minor_version), - &(g_chrome_os_version_numbers.Get().bugfix_version)); - } - } - *major_version = g_chrome_os_version_numbers.Get().major_version; - *minor_version = g_chrome_os_version_numbers.Get().minor_version; - *bugfix_version = g_chrome_os_version_numbers.Get().bugfix_version; + return GetChromeOSVersionInfo().GetVersionNumbers( + major_version, minor_version, bugfix_version); } // static -std::string SysInfo::GetLinuxStandardBaseVersionKey() { - return std::string(kLinuxStandardBaseVersionKeys[0]); +const SysInfo::LsbReleaseMap& SysInfo::GetLsbReleaseMap() { + return GetChromeOSVersionInfo().lsb_release_map(); } // static -void SysInfo::ParseLsbRelease(const std::string& lsb_release, - int32* major_version, - int32* minor_version, - int32* bugfix_version) { - size_t version_key_index = std::string::npos; - for (int i = 0; kLinuxStandardBaseVersionKeys[i] != NULL; ++i) { - version_key_index = lsb_release.find(kLinuxStandardBaseVersionKeys[i]); - if (std::string::npos != version_key_index) { - break; - } - } - if (std::string::npos == version_key_index) { - return; - } +bool SysInfo::GetLsbReleaseValue(const std::string& key, std::string* value) { + return GetChromeOSVersionInfo().GetLsbReleaseValue(key, value); +} - size_t start_index = lsb_release.find_first_of('=', version_key_index); - start_index++; // Move past '='. - size_t length = lsb_release.find_first_of('\n', start_index) - start_index; - std::string version = lsb_release.substr(start_index, length); - StringTokenizer tokenizer(version, "."); - for (int i = 0; i < 3 && tokenizer.GetNext(); ++i) { - if (0 == i) { - StringToInt(StringPiece(tokenizer.token_begin(), - tokenizer.token_end()), - major_version); - *minor_version = *bugfix_version = 0; - } else if (1 == i) { - StringToInt(StringPiece(tokenizer.token_begin(), - tokenizer.token_end()), - minor_version); - } else { // 2 == i - StringToInt(StringPiece(tokenizer.token_begin(), - tokenizer.token_end()), - bugfix_version); - } - } +// static +std::string SysInfo::GetLsbReleaseBoard() { + const char kMachineInfoBoard[] = "CHROMEOS_RELEASE_BOARD"; + std::string board; + if (!GetLsbReleaseValue(kMachineInfoBoard, &board)) + board = "unknown"; + return board; +} + +// static +Time SysInfo::GetLsbReleaseTime() { + return GetChromeOSVersionInfo().lsb_release_time(); +} + +// static +bool SysInfo::IsRunningOnChromeOS() { + return GetChromeOSVersionInfo().is_running_on_chromeos(); } // static -FilePath SysInfo::GetLsbReleaseFilePath() { - return FilePath(kLinuxStandardBaseReleaseFile); +void SysInfo::SetChromeOSVersionInfoForTest(const std::string& lsb_release, + const Time& lsb_release_time) { + scoped_ptr<Environment> env(Environment::Create()); + env->SetVar(kLsbReleaseKey, lsb_release); + env->SetVar(kLsbReleaseTimeKey, + DoubleToString(lsb_release_time.ToDoubleT())); + g_chrome_os_version_info.Get().Parse(); } } // namespace base diff --git a/chromium/base/sys_info_internal.h b/chromium/base/sys_info_internal.h new file mode 100644 index 00000000000..e7674d5c09f --- /dev/null +++ b/chromium/base/sys_info_internal.h @@ -0,0 +1,34 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_SYS_INFO_INTERNAL_H_ +#define BASE_SYS_INFO_INTERNAL_H_ + +#include "base/basictypes.h" + +namespace base { + +namespace internal { + +template<typename T, T (*F)(void)> +class LazySysInfoValue { + public: + LazySysInfoValue() + : value_(F()) { } + + ~LazySysInfoValue() { } + + T value() { return value_; } + + private: + const T value_; + + DISALLOW_COPY_AND_ASSIGN(LazySysInfoValue); +}; + +} // namespace internal + +} // namespace base + +#endif // BASE_SYS_INFO_INTERNAL_H_ diff --git a/chromium/base/sys_info_linux.cc b/chromium/base/sys_info_linux.cc index acc477134cd..6f1e5eb7d1f 100644 --- a/chromium/base/sys_info_linux.cc +++ b/chromium/base/sys_info_linux.cc @@ -7,8 +7,10 @@ #include <limits> #include "base/file_util.h" +#include "base/lazy_instance.h" #include "base/logging.h" #include "base/strings/string_number_conversions.h" +#include "base/sys_info_internal.h" namespace { @@ -22,41 +24,54 @@ int64 AmountOfMemory(int pages_name) { return static_cast<int64>(pages) * page_size; } +int64 AmountOfPhysicalMemory() { + return AmountOfMemory(_SC_PHYS_PAGES); +} + +size_t MaxSharedMemorySize() { + std::string contents; + base::ReadFileToString(base::FilePath("/proc/sys/kernel/shmmax"), &contents); + DCHECK(!contents.empty()); + if (!contents.empty() && contents[contents.length() - 1] == '\n') { + contents.erase(contents.length() - 1); + } + + int64 limit; + if (!base::StringToInt64(contents, &limit)) { + limit = 0; + } + if (limit < 0 || + static_cast<uint64>(limit) > std::numeric_limits<size_t>::max()) { + limit = 0; + } + DCHECK(limit > 0); + return static_cast<size_t>(limit); +} + +base::LazyInstance< + base::internal::LazySysInfoValue<int64, AmountOfPhysicalMemory> >::Leaky + g_lazy_physical_memory = LAZY_INSTANCE_INITIALIZER; +base::LazyInstance< + base::internal::LazySysInfoValue<size_t, MaxSharedMemorySize> >::Leaky + g_lazy_max_shared_memory = LAZY_INSTANCE_INITIALIZER; + } // namespace namespace base { // static -int64 SysInfo::AmountOfPhysicalMemory() { - return AmountOfMemory(_SC_PHYS_PAGES); +int64 SysInfo::AmountOfAvailablePhysicalMemory() { + return AmountOfMemory(_SC_AVPHYS_PAGES); } // static -int64 SysInfo::AmountOfAvailablePhysicalMemory() { - return AmountOfMemory(_SC_AVPHYS_PAGES); +int64 SysInfo::AmountOfPhysicalMemory() { + return g_lazy_physical_memory.Get().value(); } // static size_t SysInfo::MaxSharedMemorySize() { - static int64 limit; - static bool limit_valid = false; - if (!limit_valid) { - std::string contents; - ReadFileToString(FilePath("/proc/sys/kernel/shmmax"), &contents); - DCHECK(!contents.empty()); - if (!contents.empty() && contents[contents.length() - 1] == '\n') { - contents.erase(contents.length() - 1); - } - if (base::StringToInt64(contents, &limit)) { - DCHECK(limit >= 0); - DCHECK(static_cast<uint64>(limit) <= std::numeric_limits<size_t>::max()); - limit_valid = true; - } else { - NOTREACHED(); - return 0; - } - } - return static_cast<size_t>(limit); + return g_lazy_max_shared_memory.Get().value(); } // static diff --git a/chromium/base/sys_info_posix.cc b/chromium/base/sys_info_posix.cc index bbb16625590..07d08b72bbc 100644 --- a/chromium/base/sys_info_posix.cc +++ b/chromium/base/sys_info_posix.cc @@ -12,8 +12,10 @@ #include "base/basictypes.h" #include "base/file_util.h" +#include "base/lazy_instance.h" #include "base/logging.h" #include "base/strings/utf_string_conversions.h" +#include "base/sys_info_internal.h" #include "base/threading/thread_restrictions.h" #if defined(OS_ANDROID) @@ -23,10 +25,10 @@ #include <sys/statvfs.h> #endif -namespace base { +namespace { #if !defined(OS_OPENBSD) -int SysInfo::NumberOfProcessors() { +int NumberOfProcessors() { // It seems that sysconf returns the number of "logical" processors on both // Mac and Linux. So we get the number of "online logical" processors. long res = sysconf(_SC_NPROCESSORS_ONLN); @@ -37,6 +39,20 @@ int SysInfo::NumberOfProcessors() { return static_cast<int>(res); } + +base::LazyInstance< + base::internal::LazySysInfoValue<int, NumberOfProcessors> >::Leaky + g_lazy_number_of_processors = LAZY_INSTANCE_INITIALIZER; +#endif + +} // namespace + +namespace base { + +#if !defined(OS_OPENBSD) +int SysInfo::NumberOfProcessors() { + return g_lazy_number_of_processors.Get().value(); +} #endif // static diff --git a/chromium/base/sys_info_unittest.cc b/chromium/base/sys_info_unittest.cc index 8153f2b3572..93eff777c9b 100644 --- a/chromium/base/sys_info_unittest.cc +++ b/chromium/base/sys_info_unittest.cc @@ -2,9 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/environment.h" #include "base/file_util.h" #include "base/sys_info.h" #include "base/threading/platform_thread.h" +#include "base/time/time.h" #include "testing/gtest/include/gtest/gtest.h" #include "testing/platform_test.h" @@ -32,12 +34,12 @@ TEST_F(SysInfoTest, AmountOfMem) { TEST_F(SysInfoTest, AmountOfFreeDiskSpace) { // We aren't actually testing that it's correct, just that it's sane. FilePath tmp_path; - ASSERT_TRUE(file_util::GetTempDir(&tmp_path)); + ASSERT_TRUE(base::GetTempDir(&tmp_path)); EXPECT_GT(base::SysInfo::AmountOfFreeDiskSpace(tmp_path), 0) << tmp_path.value(); } -#if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_CHROMEOS) +#if defined(OS_WIN) || defined(OS_MACOSX) TEST_F(SysInfoTest, OperatingSystemVersionNumbers) { int32 os_major_version = -1; int32 os_minor_version = -1; @@ -62,17 +64,18 @@ TEST_F(SysInfoTest, Uptime) { } #if defined(OS_CHROMEOS) + TEST_F(SysInfoTest, GoogleChromeOSVersionNumbers) { int32 os_major_version = -1; int32 os_minor_version = -1; int32 os_bugfix_version = -1; - std::string lsb_release("FOO=1234123.34.5\n"); - lsb_release.append(base::SysInfo::GetLinuxStandardBaseVersionKey()); - lsb_release.append("=1.2.3.4\n"); - base::SysInfo::ParseLsbRelease(lsb_release, - &os_major_version, - &os_minor_version, - &os_bugfix_version); + const char* kLsbRelease = + "FOO=1234123.34.5\n" + "CHROMEOS_RELEASE_VERSION=1.2.3.4\n"; + base::SysInfo::SetChromeOSVersionInfoForTest(kLsbRelease, base::Time()); + base::SysInfo::OperatingSystemVersionNumbers(&os_major_version, + &os_minor_version, + &os_bugfix_version); EXPECT_EQ(1, os_major_version); EXPECT_EQ(2, os_minor_version); EXPECT_EQ(3, os_bugfix_version); @@ -82,13 +85,13 @@ TEST_F(SysInfoTest, GoogleChromeOSVersionNumbersFirst) { int32 os_major_version = -1; int32 os_minor_version = -1; int32 os_bugfix_version = -1; - std::string lsb_release(base::SysInfo::GetLinuxStandardBaseVersionKey()); - lsb_release.append("=1.2.3.4\n"); - lsb_release.append("FOO=1234123.34.5\n"); - base::SysInfo::ParseLsbRelease(lsb_release, - &os_major_version, - &os_minor_version, - &os_bugfix_version); + const char* kLsbRelease = + "CHROMEOS_RELEASE_VERSION=1.2.3.4\n" + "FOO=1234123.34.5\n"; + base::SysInfo::SetChromeOSVersionInfoForTest(kLsbRelease, base::Time()); + base::SysInfo::OperatingSystemVersionNumbers(&os_major_version, + &os_minor_version, + &os_bugfix_version); EXPECT_EQ(1, os_major_version); EXPECT_EQ(2, os_minor_version); EXPECT_EQ(3, os_bugfix_version); @@ -98,14 +101,46 @@ TEST_F(SysInfoTest, GoogleChromeOSNoVersionNumbers) { int32 os_major_version = -1; int32 os_minor_version = -1; int32 os_bugfix_version = -1; - std::string lsb_release("FOO=1234123.34.5\n"); - base::SysInfo::ParseLsbRelease(lsb_release, - &os_major_version, - &os_minor_version, - &os_bugfix_version); - EXPECT_EQ(-1, os_major_version); - EXPECT_EQ(-1, os_minor_version); - EXPECT_EQ(-1, os_bugfix_version); + const char* kLsbRelease = "FOO=1234123.34.5\n"; + base::SysInfo::SetChromeOSVersionInfoForTest(kLsbRelease, base::Time()); + base::SysInfo::OperatingSystemVersionNumbers(&os_major_version, + &os_minor_version, + &os_bugfix_version); + EXPECT_EQ(0, os_major_version); + EXPECT_EQ(0, os_minor_version); + EXPECT_EQ(0, os_bugfix_version); +} + +TEST_F(SysInfoTest, GoogleChromeOSLsbReleaseTime) { + const char* kLsbRelease = "CHROMEOS_RELEASE_VERSION=1.2.3.4"; + // Use a fake time that can be safely displayed as a string. + const base::Time lsb_release_time(base::Time::FromDoubleT(12345.6)); + base::SysInfo::SetChromeOSVersionInfoForTest(kLsbRelease, lsb_release_time); + base::Time parsed_lsb_release_time = base::SysInfo::GetLsbReleaseTime(); + EXPECT_DOUBLE_EQ(lsb_release_time.ToDoubleT(), + parsed_lsb_release_time.ToDoubleT()); +} + +TEST_F(SysInfoTest, IsRunningOnChromeOS) { + base::SysInfo::SetChromeOSVersionInfoForTest("", base::Time()); + EXPECT_FALSE(base::SysInfo::IsRunningOnChromeOS()); + + const char* kLsbRelease1 = + "CHROMEOS_RELEASE_NAME=Non Chrome OS\n" + "CHROMEOS_RELEASE_VERSION=1.2.3.4\n"; + base::SysInfo::SetChromeOSVersionInfoForTest(kLsbRelease1, base::Time()); + EXPECT_FALSE(base::SysInfo::IsRunningOnChromeOS()); + + const char* kLsbRelease2 = + "CHROMEOS_RELEASE_NAME=Chrome OS\n" + "CHROMEOS_RELEASE_VERSION=1.2.3.4\n"; + base::SysInfo::SetChromeOSVersionInfoForTest(kLsbRelease2, base::Time()); + EXPECT_TRUE(base::SysInfo::IsRunningOnChromeOS()); + + const char* kLsbRelease3 = + "CHROMEOS_RELEASE_NAME=Chromium OS\n"; + base::SysInfo::SetChromeOSVersionInfoForTest(kLsbRelease3, base::Time()); + EXPECT_TRUE(base::SysInfo::IsRunningOnChromeOS()); } #endif // OS_CHROMEOS diff --git a/chromium/base/template_util.h b/chromium/base/template_util.h index e21d4dc7737..f4bf7461327 100644 --- a/chromium/base/template_util.h +++ b/chromium/base/template_util.h @@ -28,6 +28,39 @@ typedef integral_constant<bool, false> false_type; template <class T> struct is_pointer : false_type {}; template <class T> struct is_pointer<T*> : true_type {}; +// Member function pointer detection up to four params. Add more as needed +// below. This is built-in to C++ 11, and we can remove this when we switch. +template<typename T> +struct is_member_function_pointer : false_type {}; + +template <typename R, typename Z> +struct is_member_function_pointer<R(Z::*)()> : true_type {}; +template <typename R, typename Z> +struct is_member_function_pointer<R(Z::*)() const> : true_type {}; + +template <typename R, typename Z, typename A> +struct is_member_function_pointer<R(Z::*)(A)> : true_type {}; +template <typename R, typename Z, typename A> +struct is_member_function_pointer<R(Z::*)(A) const> : true_type {}; + +template <typename R, typename Z, typename A, typename B> +struct is_member_function_pointer<R(Z::*)(A, B)> : true_type {}; +template <typename R, typename Z, typename A, typename B> +struct is_member_function_pointer<R(Z::*)(A, B) const> : true_type {}; + +template <typename R, typename Z, typename A, typename B, typename C> +struct is_member_function_pointer<R(Z::*)(A, B, C)> : true_type {}; +template <typename R, typename Z, typename A, typename B, typename C> +struct is_member_function_pointer<R(Z::*)(A, B, C) const> : true_type {}; + +template <typename R, typename Z, typename A, typename B, typename C, + typename D> +struct is_member_function_pointer<R(Z::*)(A, B, C, D)> : true_type {}; +template <typename R, typename Z, typename A, typename B, typename C, + typename D> +struct is_member_function_pointer<R(Z::*)(A, B, C, D) const> : true_type {}; + + template <class T, class U> struct is_same : public false_type {}; template <class T> struct is_same<T,T> : true_type {}; @@ -39,6 +72,9 @@ template <class T> struct is_non_const_reference : false_type {}; template <class T> struct is_non_const_reference<T&> : true_type {}; template <class T> struct is_non_const_reference<const T&> : false_type {}; +template <class T> struct is_const : false_type {}; +template <class T> struct is_const<const T> : true_type {}; + template <class T> struct is_void : false_type {}; template <> struct is_void<void> : true_type {}; @@ -103,6 +139,12 @@ struct is_class sizeof(internal::YesType)> { }; +template<bool B, class T = void> +struct enable_if {}; + +template<class T> +struct enable_if<true, T> { typedef T type; }; + } // namespace base #endif // BASE_TEMPLATE_UTIL_H_ diff --git a/chromium/base/template_util_unittest.cc b/chromium/base/template_util_unittest.cc index 4cfa3e4fb5a..98ad9389122 100644 --- a/chromium/base/template_util_unittest.cc +++ b/chromium/base/template_util_unittest.cc @@ -76,5 +76,55 @@ COMPILE_ASSERT(!is_class<char*>::value, IsClass); COMPILE_ASSERT(!is_class<int&>::value, IsClass); COMPILE_ASSERT(!is_class<char[3]>::value, IsClass); + +COMPILE_ASSERT(!is_member_function_pointer<int>::value, + IsMemberFunctionPointer); +COMPILE_ASSERT(!is_member_function_pointer<int*>::value, + IsMemberFunctionPointer); +COMPILE_ASSERT(!is_member_function_pointer<void*>::value, + IsMemberFunctionPointer); +COMPILE_ASSERT(!is_member_function_pointer<AStruct>::value, + IsMemberFunctionPointer); +COMPILE_ASSERT(!is_member_function_pointer<AStruct*>::value, + IsMemberFunctionPointer); +COMPILE_ASSERT(!is_member_function_pointer<int(*)(int)>::value, + IsMemberFunctionPointer); +COMPILE_ASSERT(!is_member_function_pointer<int(*)(int, int)>::value, + IsMemberFunctionPointer); + +COMPILE_ASSERT(is_member_function_pointer<void (AStruct::*)()>::value, + IsMemberFunctionPointer); +COMPILE_ASSERT(is_member_function_pointer<void (AStruct::*)(int)>::value, + IsMemberFunctionPointer); +COMPILE_ASSERT(is_member_function_pointer<int (AStruct::*)(int)>::value, + IsMemberFunctionPointer); +COMPILE_ASSERT(is_member_function_pointer<int (AStruct::*)(int) const>::value, + IsMemberFunctionPointer); +COMPILE_ASSERT(is_member_function_pointer<int (AStruct::*)(int, int)>::value, + IsMemberFunctionPointer); +COMPILE_ASSERT(is_member_function_pointer< + int (AStruct::*)(int, int) const>::value, + IsMemberFunctionPointer); +COMPILE_ASSERT(is_member_function_pointer< + int (AStruct::*)(int, int, int)>::value, + IsMemberFunctionPointer); +COMPILE_ASSERT(is_member_function_pointer< + int (AStruct::*)(int, int, int) const>::value, + IsMemberFunctionPointer); +COMPILE_ASSERT(is_member_function_pointer< + int (AStruct::*)(int, int, int, int)>::value, + IsMemberFunctionPointer); +COMPILE_ASSERT(is_member_function_pointer< + int (AStruct::*)(int, int, int, int) const>::value, + IsMemberFunctionPointer); + +// False because we don't have a specialization for 5 params yet. +COMPILE_ASSERT(!is_member_function_pointer< + int (AStruct::*)(int, int, int, int, int)>::value, + IsMemberFunctionPointer); +COMPILE_ASSERT(!is_member_function_pointer< + int (AStruct::*)(int, int, int, int, int) const>::value, + IsMemberFunctionPointer); + } // namespace } // namespace base diff --git a/chromium/base/third_party/dmg_fp/README.chromium b/chromium/base/third_party/dmg_fp/README.chromium index 10db40838bf..33ab78b9fe7 100644 --- a/chromium/base/third_party/dmg_fp/README.chromium +++ b/chromium/base/third_party/dmg_fp/README.chromium @@ -19,4 +19,4 @@ List of changes made to original code: float_precision_crash.patch and crbug.com/123157 - Fix for 'warning C4703: potentially uninitialized local pointer variable' in VS2012. - + - Disable optimization on VS2013 pending fix of compiler optimization bug. diff --git a/chromium/base/third_party/dmg_fp/dtoa.cc b/chromium/base/third_party/dmg_fp/dtoa.cc index 4eb9f0efd94..b03ccff569f 100644 --- a/chromium/base/third_party/dmg_fp/dtoa.cc +++ b/chromium/base/third_party/dmg_fp/dtoa.cc @@ -179,6 +179,12 @@ * used for input more than STRTOD_DIGLIM digits long (default 40). */ +#if defined _MSC_VER && _MSC_VER == 1800 +// TODO(scottmg): VS2013 RC ICEs on a bunch of functions in this file. +// This should be removed after RTM. See http://crbug.com/288948. +#pragma optimize("", off) +#endif + #define IEEE_8087 #define NO_HEX_FP diff --git a/chromium/base/third_party/dmg_fp/vs2013-optimization.patch b/chromium/base/third_party/dmg_fp/vs2013-optimization.patch new file mode 100644 index 00000000000..d91b370e51f --- /dev/null +++ b/chromium/base/third_party/dmg_fp/vs2013-optimization.patch @@ -0,0 +1,18 @@ +Index: base/third_party/dmg_fp/dtoa.cc +diff --git a/base/third_party/dmg_fp/dtoa.cc b/base/third_party/dmg_fp/dtoa.cc +index 4eb9f0efd94221b3ab95f84554bbc92f112bf973..b03ccff569f9403eb67a95737b0e19740e56ef33 100644 +--- a/base/third_party/dmg_fp/dtoa.cc ++++ b/base/third_party/dmg_fp/dtoa.cc +@@ -179,6 +179,12 @@ + * used for input more than STRTOD_DIGLIM digits long (default 40). + */ + ++#if defined _MSC_VER && _MSC_VER == 1800 ++// TODO(scottmg): VS2013 RC ICEs on a bunch of functions in this file. ++// This should be removed after RTM. See http://crbug.com/288948. ++#pragma optimize("", off) ++#endif ++ + #define IEEE_8087 + #define NO_HEX_FP + diff --git a/chromium/base/third_party/icu/icu_utf.h b/chromium/base/third_party/icu/icu_utf.h index 9cb12472732..a604e5f5670 100644 --- a/chromium/base/third_party/icu/icu_utf.h +++ b/chromium/base/third_party/icu/icu_utf.h @@ -22,6 +22,7 @@ namespace base_icu { typedef uint32 UChar32; +typedef uint16 UChar; typedef int8 UBool; // General --------------------------------------------------------------------- @@ -304,7 +305,8 @@ UChar32 utf8_nextCharSafeBody(const uint8 *s, int32 *pi, int32 length, UChar32 c * @return lead surrogate (U+d800..U+dbff) for supplementary * @stable ICU 2.4 */ -#define CBU16_LEAD(supplementary) (UChar)(((supplementary)>>10)+0xd7c0) +#define CBU16_LEAD(supplementary) \ + (base_icu::UChar)(((supplementary)>>10)+0xd7c0) /** * Get the trail surrogate (0xdc00..0xdfff) for a @@ -313,7 +315,8 @@ UChar32 utf8_nextCharSafeBody(const uint8 *s, int32 *pi, int32 length, UChar32 c * @return trail surrogate (U+dc00..U+dfff) for supplementary * @stable ICU 2.4 */ -#define CBU16_TRAIL(supplementary) (UChar)(((supplementary)&0x3ff)|0xdc00) +#define CBU16_TRAIL(supplementary) \ + (base_icu::UChar)(((supplementary)&0x3ff)|0xdc00) /** * How many 16-bit code units are used to encode this Unicode code point? (1 or 2) diff --git a/chromium/base/threading/non_thread_safe.h b/chromium/base/threading/non_thread_safe.h index cf7a41818da..a27bb0ea0c5 100644 --- a/chromium/base/threading/non_thread_safe.h +++ b/chromium/base/threading/non_thread_safe.h @@ -56,7 +56,7 @@ class NonThreadSafeDoNothing { // } // // Note that base::ThreadChecker offers identical functionality to -// NonThreadSafe, but does not require inheritence. In general, it is preferable +// NonThreadSafe, but does not require inheritance. In general, it is preferable // to have a base::ThreadChecker as a member, rather than inherit from // NonThreadSafe. For more details about when to choose one over the other, see // the documentation for base::ThreadChecker. diff --git a/chromium/base/threading/sequenced_worker_pool.h b/chromium/base/threading/sequenced_worker_pool.h index 1bd275b206d..d3c85e23ae4 100644 --- a/chromium/base/threading/sequenced_worker_pool.h +++ b/chromium/base/threading/sequenced_worker_pool.h @@ -69,6 +69,9 @@ class SequencedTaskRunner; // // Note that SequencedWorkerPool is RefCountedThreadSafe (inherited // from TaskRunner). +// +// Test-only code should wrap this in a base::SequencedWorkerPoolOwner to avoid +// memory leaks. See http://crbug.com/273800 class BASE_EXPORT SequencedWorkerPool : public TaskRunner { public: // Defines what should happen to a task posted to the worker pool on diff --git a/chromium/base/threading/thread.cc b/chromium/base/threading/thread.cc index f0337750356..ae4d3731947 100644 --- a/chromium/base/threading/thread.cc +++ b/chromium/base/threading/thread.cc @@ -5,7 +5,6 @@ #include "base/threading/thread.h" #include "base/bind.h" -#include "base/debug/alias.h" #include "base/lazy_instance.h" #include "base/third_party/dynamic_annotations/dynamic_annotations.h" #include "base/threading/thread_id_name_manager.h" @@ -50,6 +49,20 @@ struct Thread::StartupData { event(false, false) {} }; +Thread::Options::Options() + : message_loop_type(MessageLoop::TYPE_DEFAULT), + stack_size(0) { +} + +Thread::Options::Options(MessageLoop::Type type, + size_t size) + : message_loop_type(type), + stack_size(size) { +} + +Thread::Options::~Options() { +} + Thread::Thread(const char* name) : #if defined(OS_WIN) @@ -173,23 +186,16 @@ bool Thread::GetThreadWasQuitProperly() { void Thread::ThreadMain() { { -#if defined(OS_MACOSX) - // Store the thread name on the stack to debug <http://crbug.com/274705>. - // End with a byte sequence of <EOT><BEL><NUL> to make it easier to grep in - // the minidump stack dump. - const size_t kThreadNameSize = 50; - char thread_name[kThreadNameSize]; - strncpy(thread_name, name_.c_str(), kThreadNameSize); - thread_name[kThreadNameSize - 1] = '\0'; - thread_name[kThreadNameSize - 2] = '\7'; - thread_name[kThreadNameSize - 3] = '\3'; - base::debug::Alias(thread_name); -#endif - // The message loop for this thread. // Allocated on the heap to centralize any leak reports at this line. - scoped_ptr<MessageLoop> message_loop( - new MessageLoop(startup_data_->options.message_loop_type)); + scoped_ptr<MessageLoop> message_loop; + if (!startup_data_->options.message_pump_factory.is_null()) { + message_loop.reset( + new MessageLoop(startup_data_->options.message_pump_factory.Run())); + } else { + message_loop.reset( + new MessageLoop(startup_data_->options.message_loop_type)); + } // Complete the initialization of our Thread object. thread_id_ = PlatformThread::CurrentId(); @@ -231,10 +237,6 @@ void Thread::ThreadMain() { // We can't receive messages anymore. message_loop_ = NULL; - -#if defined(OS_MACOSX) - base::debug::Alias(thread_name); -#endif } } diff --git a/chromium/base/threading/thread.h b/chromium/base/threading/thread.h index 98831b82e5c..99f9dd4a597 100644 --- a/chromium/base/threading/thread.h +++ b/chromium/base/threading/thread.h @@ -8,12 +8,16 @@ #include <string> #include "base/base_export.h" +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" #include "base/message_loop/message_loop.h" #include "base/message_loop/message_loop_proxy.h" #include "base/threading/platform_thread.h" namespace base { +class MessagePump; + // A simple thread abstraction that establishes a MessageLoop on a new thread. // The consumer uses the MessageLoop of the thread to cause code to execute on // the thread. When this object is destroyed the thread is terminated. All @@ -29,14 +33,23 @@ namespace base { // (3.b) MessageLoop::DestructionObserver::WillDestroyCurrentMessageLoop class BASE_EXPORT Thread : PlatformThread::Delegate { public: - struct Options { - Options() : message_loop_type(MessageLoop::TYPE_DEFAULT), stack_size(0) {} - Options(MessageLoop::Type type, size_t size) - : message_loop_type(type), stack_size(size) {} + struct BASE_EXPORT Options { + typedef Callback<scoped_ptr<MessagePump>()> MessagePumpFactory; + + Options(); + Options(MessageLoop::Type type, size_t size); + ~Options(); // Specifies the type of message loop that will be allocated on the thread. + // This is ignored if message_pump_factory.is_null() is false. MessageLoop::Type message_loop_type; + // Used to create the MessagePump for the MessageLoop. The callback is Run() + // on the thread. If message_pump_factory.is_null(), then a MessagePump + // appropriate for |message_loop_type| is created. Setting this forces the + // MessageLoop::Type to TYPE_CUSTOM. + MessagePumpFactory message_pump_factory; + // Specifies the maximum stack size that the thread is allowed to use. // This does not necessarily correspond to the thread's initial stack size. // A value of 0 indicates that the default maximum should be used. diff --git a/chromium/base/threading/thread_checker.h b/chromium/base/threading/thread_checker.h index 5a8ef2261d7..a2ab3fe4fab 100644 --- a/chromium/base/threading/thread_checker.h +++ b/chromium/base/threading/thread_checker.h @@ -46,7 +46,7 @@ class ThreadCheckerDoNothing { // // While inheriting from base::NonThreadSafe may give a clear indication about // the thread-safety of a class, it may also lead to violations of the style -// guide with regard to multiple inheritence. The choice between having a +// guide with regard to multiple inheritance. The choice between having a // ThreadChecker member and inheriting from base::NonThreadSafe should be based // on whether: // - Derived classes need to know the thread they belong to, as opposed to diff --git a/chromium/base/threading/thread_local.h b/chromium/base/threading/thread_local.h index 6561420758c..b13be1ab467 100644 --- a/chromium/base/threading/thread_local.h +++ b/chromium/base/threading/thread_local.h @@ -56,7 +56,6 @@ #endif namespace base { - namespace internal { // Helper functions that abstract the cross-platform APIs. Do not use directly. @@ -67,10 +66,10 @@ struct BASE_EXPORT ThreadLocalPlatform { typedef pthread_key_t SlotType; #endif - static void AllocateSlot(SlotType& slot); - static void FreeSlot(SlotType& slot); - static void* GetValueFromSlot(SlotType& slot); - static void SetValueInSlot(SlotType& slot, void* value); + static void AllocateSlot(SlotType* slot); + static void FreeSlot(SlotType slot); + static void* GetValueFromSlot(SlotType slot); + static void SetValueInSlot(SlotType slot, void* value); }; } // namespace internal @@ -79,7 +78,7 @@ template <typename Type> class ThreadLocalPointer { public: ThreadLocalPointer() : slot_() { - internal::ThreadLocalPlatform::AllocateSlot(slot_); + internal::ThreadLocalPlatform::AllocateSlot(&slot_); } ~ThreadLocalPointer() { @@ -106,8 +105,8 @@ class ThreadLocalPointer { class ThreadLocalBoolean { public: - ThreadLocalBoolean() { } - ~ThreadLocalBoolean() { } + ThreadLocalBoolean() {} + ~ThreadLocalBoolean() {} bool Get() { return tlp_.Get() != NULL; diff --git a/chromium/base/threading/thread_local_posix.cc b/chromium/base/threading/thread_local_posix.cc index 49510064e75..526a66d0629 100644 --- a/chromium/base/threading/thread_local_posix.cc +++ b/chromium/base/threading/thread_local_posix.cc @@ -9,32 +9,30 @@ #include "base/logging.h" namespace base { - namespace internal { // static -void ThreadLocalPlatform::AllocateSlot(SlotType& slot) { - int error = pthread_key_create(&slot, NULL); +void ThreadLocalPlatform::AllocateSlot(SlotType* slot) { + int error = pthread_key_create(slot, NULL); CHECK_EQ(error, 0); } // static -void ThreadLocalPlatform::FreeSlot(SlotType& slot) { +void ThreadLocalPlatform::FreeSlot(SlotType slot) { int error = pthread_key_delete(slot); DCHECK_EQ(0, error); } // static -void* ThreadLocalPlatform::GetValueFromSlot(SlotType& slot) { +void* ThreadLocalPlatform::GetValueFromSlot(SlotType slot) { return pthread_getspecific(slot); } // static -void ThreadLocalPlatform::SetValueInSlot(SlotType& slot, void* value) { +void ThreadLocalPlatform::SetValueInSlot(SlotType slot, void* value) { int error = pthread_setspecific(slot, value); DCHECK_EQ(error, 0); } } // namespace internal - } // namespace base diff --git a/chromium/base/threading/thread_local_win.cc b/chromium/base/threading/thread_local_win.cc index 56d3a3ac7f2..1c74e421387 100644 --- a/chromium/base/threading/thread_local_win.cc +++ b/chromium/base/threading/thread_local_win.cc @@ -9,34 +9,32 @@ #include "base/logging.h" namespace base { - namespace internal { // static -void ThreadLocalPlatform::AllocateSlot(SlotType& slot) { - slot = TlsAlloc(); - CHECK_NE(slot, TLS_OUT_OF_INDEXES); +void ThreadLocalPlatform::AllocateSlot(SlotType* slot) { + *slot = TlsAlloc(); + CHECK_NE(*slot, TLS_OUT_OF_INDEXES); } // static -void ThreadLocalPlatform::FreeSlot(SlotType& slot) { +void ThreadLocalPlatform::FreeSlot(SlotType slot) { if (!TlsFree(slot)) { NOTREACHED() << "Failed to deallocate tls slot with TlsFree()."; } } // static -void* ThreadLocalPlatform::GetValueFromSlot(SlotType& slot) { +void* ThreadLocalPlatform::GetValueFromSlot(SlotType slot) { return TlsGetValue(slot); } // static -void ThreadLocalPlatform::SetValueInSlot(SlotType& slot, void* value) { +void ThreadLocalPlatform::SetValueInSlot(SlotType slot, void* value) { if (!TlsSetValue(slot, value)) { LOG(FATAL) << "Failed to TlsSetValue()."; } } } // namespace internal - } // namespace base diff --git a/chromium/base/threading/watchdog.cc b/chromium/base/threading/watchdog.cc index a18efecc5f6..82c187564b1 100644 --- a/chromium/base/threading/watchdog.cc +++ b/chromium/base/threading/watchdog.cc @@ -20,14 +20,18 @@ namespace { // Without this safety net, any alarm will typically trigger a host of follow // on alarms from callers that specify old times. -// Lock for access of static data... -LazyInstance<Lock>::Leaky g_static_lock = LAZY_INSTANCE_INITIALIZER; +struct StaticData { + // Lock for access of static data... + Lock lock; -// When did we last alarm and get stuck (for a while) in a debugger? -TimeTicks g_last_debugged_alarm_time; + // When did we last alarm and get stuck (for a while) in a debugger? + TimeTicks last_debugged_alarm_time; -// How long did we sit on a break in the debugger? -TimeDelta g_last_debugged_alarm_delay; + // How long did we sit on a break in the debugger? + TimeDelta last_debugged_alarm_delay; +}; + +LazyInstance<StaticData>::Leaky g_static_data = LAZY_INSTANCE_INITIALIZER; } // namespace @@ -115,6 +119,7 @@ void Watchdog::Alarm() { void Watchdog::ThreadDelegate::ThreadMain() { SetThreadName(); TimeDelta remaining_duration; + StaticData* static_data = g_static_data.Pointer(); while (1) { AutoLock lock(watchdog_->lock_); while (DISARMED == watchdog_->state_) @@ -134,12 +139,12 @@ void Watchdog::ThreadDelegate::ThreadMain() { // We overslept, so this seems like a real alarm. // Watch out for a user that stopped the debugger on a different alarm! { - AutoLock static_lock(*g_static_lock.Pointer()); - if (g_last_debugged_alarm_time > watchdog_->start_time_) { + AutoLock static_lock(static_data->lock); + if (static_data->last_debugged_alarm_time > watchdog_->start_time_) { // False alarm: we started our clock before the debugger break (last // alarm time). - watchdog_->start_time_ += g_last_debugged_alarm_delay; - if (g_last_debugged_alarm_time > watchdog_->start_time_) + watchdog_->start_time_ += static_data->last_debugged_alarm_delay; + if (static_data->last_debugged_alarm_time > watchdog_->start_time_) // Too many alarms must have taken place. watchdog_->state_ = DISARMED; continue; @@ -155,10 +160,10 @@ void Watchdog::ThreadDelegate::ThreadMain() { if (last_alarm_delay <= TimeDelta::FromMilliseconds(2)) continue; // Ignore race of two alarms/breaks going off at roughly the same time. - AutoLock static_lock(*g_static_lock.Pointer()); + AutoLock static_lock(static_data->lock); // This was a real debugger break. - g_last_debugged_alarm_time = last_alarm_time; - g_last_debugged_alarm_delay = last_alarm_delay; + static_data->last_debugged_alarm_time = last_alarm_time; + static_data->last_debugged_alarm_delay = last_alarm_delay; } } @@ -170,9 +175,10 @@ void Watchdog::ThreadDelegate::SetThreadName() const { // static void Watchdog::ResetStaticData() { - AutoLock lock(*g_static_lock.Pointer()); - g_last_debugged_alarm_time = TimeTicks(); - g_last_debugged_alarm_delay = TimeDelta(); + StaticData* static_data = g_static_data.Pointer(); + AutoLock lock(static_data->lock); + static_data->last_debugged_alarm_time = TimeTicks(); + static_data->last_debugged_alarm_delay = TimeDelta(); } } // namespace base diff --git a/chromium/base/threading/worker_pool_posix_unittest.cc b/chromium/base/threading/worker_pool_posix_unittest.cc index 49f6570aa4e..862ffddcf89 100644 --- a/chromium/base/threading/worker_pool_posix_unittest.cc +++ b/chromium/base/threading/worker_pool_posix_unittest.cc @@ -160,7 +160,6 @@ TEST_F(PosixDynamicThreadPoolTest, Basic) { EXPECT_EQ(1U, unique_threads_.size()) << "There should be only one thread allocated for one task."; - EXPECT_EQ(1, peer_.num_idle_threads()); EXPECT_EQ(1, counter_); } diff --git a/chromium/base/time/time.cc b/chromium/base/time/time.cc index a9e4b1cf90a..9f3c53d6695 100644 --- a/chromium/base/time/time.cc +++ b/chromium/base/time/time.cc @@ -8,6 +8,7 @@ #include <ostream> #include "base/float_util.h" +#include "base/lazy_instance.h" #include "base/logging.h" #include "base/third_party/nspr/prtime.h" #include "base/third_party/nspr/prtypes.h" @@ -189,6 +190,29 @@ bool Time::FromStringInternal(const char* time_string, return true; } +// Local helper class to hold the conversion from Time to TickTime at the +// time of the Unix epoch. +class UnixEpochSingleton { + public: + UnixEpochSingleton() + : unix_epoch_(TimeTicks::Now() - (Time::Now() - Time::UnixEpoch())) {} + + TimeTicks unix_epoch() const { return unix_epoch_; } + + private: + const TimeTicks unix_epoch_; + + DISALLOW_COPY_AND_ASSIGN(UnixEpochSingleton); +}; + +static LazyInstance<UnixEpochSingleton>::Leaky + leaky_unix_epoch_singleton_instance = LAZY_INSTANCE_INITIALIZER; + +// Static +TimeTicks TimeTicks::UnixEpoch() { + return leaky_unix_epoch_singleton_instance.Get().unix_epoch(); +} + // Time::Exploded ------------------------------------------------------------- inline bool is_in_range(int value, int lo, int hi) { diff --git a/chromium/base/time/time.h b/chromium/base/time/time.h index a236a3f09a0..40566b92317 100644 --- a/chromium/base/time/time.h +++ b/chromium/base/time/time.h @@ -544,9 +544,12 @@ class BASE_EXPORT TimeTicks { // SHOULD ONLY BE USED WHEN IT IS REALLY NEEDED. static TimeTicks HighResNow(); + static bool IsHighResNowFastAndReliable(); + // Returns true if ThreadNow() is supported on this system. static bool IsThreadNowSupported() { -#if defined(_POSIX_THREAD_CPUTIME) && (_POSIX_THREAD_CPUTIME >= 0) +#if (defined(_POSIX_THREAD_CPUTIME) && (_POSIX_THREAD_CPUTIME >= 0)) || \ + (defined(OS_MACOSX) && !defined(OS_IOS)) || defined(OS_ANDROID) return true; #else return false; @@ -602,6 +605,14 @@ class BASE_EXPORT TimeTicks { return TimeTicks(ticks); } + // Get the TimeTick value at the time of the UnixEpoch. This is useful when + // you need to relate the value of TimeTicks to a real time and date. + // Note: Upon first invocation, this function takes a snapshot of the realtime + // clock to establish a reference point. This function will return the same + // value for the duration of the application, but will be different in future + // application runs. + static TimeTicks UnixEpoch(); + // Returns the internal numeric value of the TimeTicks object. // For serializing, use FromInternalValue to reconstitute. int64 ToInternalValue() const { diff --git a/chromium/base/time/time_mac.cc b/chromium/base/time/time_mac.cc index da46aa7ee51..2388ddc6e69 100644 --- a/chromium/base/time/time_mac.cc +++ b/chromium/base/time/time_mac.cc @@ -6,6 +6,7 @@ #include <CoreFoundation/CFDate.h> #include <CoreFoundation/CFTimeZone.h> +#include <mach/mach.h> #include <mach/mach_time.h> #include <sys/sysctl.h> #include <sys/time.h> @@ -15,6 +16,7 @@ #include "base/basictypes.h" #include "base/logging.h" #include "base/mac/scoped_cftyperef.h" +#include "base/mac/scoped_mach_port.h" namespace { @@ -64,6 +66,33 @@ uint64_t ComputeCurrentTicks() { #endif // defined(OS_IOS) } +uint64_t ComputeThreadTicks() { +#if defined(OS_IOS) + NOTREACHED(); + return 0; +#else + base::mac::ScopedMachPort thread(mach_thread_self()); + mach_msg_type_number_t thread_info_count = THREAD_BASIC_INFO_COUNT; + thread_basic_info_data_t thread_info_data; + + if (thread == MACH_PORT_NULL) { + DLOG(ERROR) << "Failed to get mach_thread_self()"; + return 0; + } + + kern_return_t kr = thread_info( + thread, + THREAD_BASIC_INFO, + reinterpret_cast<thread_info_t>(&thread_info_data), + &thread_info_count); + DCHECK_EQ(KERN_SUCCESS, kr); + + return (thread_info_data.user_time.seconds * + base::Time::kMicrosecondsPerSecond) + + thread_info_data.user_time.microseconds; +#endif // defined(OS_IOS) +} + } // namespace namespace base { @@ -85,8 +114,6 @@ namespace base { // irb(main):011:0> Time.at(-11644473600).getutc() // => Mon Jan 01 00:00:00 UTC 1601 static const int64 kWindowsEpochDeltaSeconds = GG_INT64_C(11644473600); -static const int64 kWindowsEpochDeltaMilliseconds = - kWindowsEpochDeltaSeconds * Time::kMillisecondsPerSecond; // static const int64 Time::kWindowsEpochDeltaMicroseconds = @@ -192,9 +219,13 @@ TimeTicks TimeTicks::HighResNow() { } // static +bool TimeTicks::IsHighResNowFastAndReliable() { + return true; +} + +// static TimeTicks TimeTicks::ThreadNow() { - NOTREACHED(); - return TimeTicks(); + return TimeTicks(ComputeThreadTicks()); } // static diff --git a/chromium/base/time/time_posix.cc b/chromium/base/time/time_posix.cc index dc551b0dab6..3378038fe44 100644 --- a/chromium/base/time/time_posix.cc +++ b/chromium/base/time/time_posix.cc @@ -28,6 +28,7 @@ namespace { +#if !defined(OS_MACOSX) // Define a system-specific SysTime that wraps either to a time_t or // a time64_t depending on the host system, and associated convertion. // See crbug.com/162007 @@ -66,7 +67,6 @@ void SysTimeToTimeStruct(SysTime t, struct tm* timestruct, bool is_local) { } #endif // OS_ANDROID -#if !defined(OS_MACOSX) // Helper function to get results from clock_gettime() as TimeTicks object. // Minimum requirement is MONOTONIC_CLOCK to be supported on the system. // FreeBSD 6 has CLOCK_MONOTONIC but defines _POSIX_MONOTONIC_CLOCK to -1. @@ -124,8 +124,6 @@ struct timespec TimeDelta::ToTimeSpec() const { // irb(main):011:0> Time.at(-11644473600).getutc() // => Mon Jan 01 00:00:00 UTC 1601 static const int64 kWindowsEpochDeltaSeconds = GG_INT64_C(11644473600); -static const int64 kWindowsEpochDeltaMilliseconds = - kWindowsEpochDeltaSeconds * Time::kMillisecondsPerSecond; // static const int64 Time::kWindowsEpochDeltaMicroseconds = @@ -307,8 +305,14 @@ TimeTicks TimeTicks::HighResNow() { } // static +bool TimeTicks::IsHighResNowFastAndReliable() { + return true; +} + +// static TimeTicks TimeTicks::ThreadNow() { -#if defined(_POSIX_THREAD_CPUTIME) && (_POSIX_THREAD_CPUTIME >= 0) +#if (defined(_POSIX_THREAD_CPUTIME) && (_POSIX_THREAD_CPUTIME >= 0)) || \ + defined(OS_ANDROID) return ClockNow(CLOCK_THREAD_CPUTIME_ID); #else NOTREACHED(); diff --git a/chromium/base/time/time_unittest.cc b/chromium/base/time/time_unittest.cc index 81a3358f08c..e78a61cf4bd 100644 --- a/chromium/base/time/time_unittest.cc +++ b/chromium/base/time/time_unittest.cc @@ -639,12 +639,17 @@ TEST(TimeTicks, ThreadNow) { if (TimeTicks::IsThreadNowSupported()) { TimeTicks begin = TimeTicks::Now(); TimeTicks begin_thread = TimeTicks::ThreadNow(); - // Sleep for 10 milliseconds to get the thread de-scheduled + // Make sure that ThreadNow value is non-zero. + EXPECT_GT(begin_thread, TimeTicks()); + // Sleep for 10 milliseconds to get the thread de-scheduled. base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10)); TimeTicks end_thread = TimeTicks::ThreadNow(); TimeTicks end = TimeTicks::Now(); TimeDelta delta = end - begin; TimeDelta delta_thread = end_thread - begin_thread; + // Make sure that some thread time have elapsed. + EXPECT_GT(delta_thread.InMicroseconds(), 0); + // But the thread time is at least 9ms less than clock time. TimeDelta difference = delta - delta_thread; EXPECT_GE(difference.InMicroseconds(), 9000); } diff --git a/chromium/base/time/time_win.cc b/chromium/base/time/time_win.cc index f35f735bfbd..ef8d29a92cb 100644 --- a/chromium/base/time/time_win.cc +++ b/chromium/base/time/time_win.cc @@ -489,6 +489,11 @@ TimeTicks TimeTicks::HighResNow() { } // static +bool TimeTicks::IsHighResNowFastAndReliable() { + return CPUReliablySupportsHighResTime(); +} + +// static TimeTicks TimeTicks::ThreadNow() { NOTREACHED(); return TimeTicks(); diff --git a/chromium/base/timer/elapsed_timer.cc b/chromium/base/timer/elapsed_timer.cc new file mode 100644 index 00000000000..f2a2f7113e8 --- /dev/null +++ b/chromium/base/timer/elapsed_timer.cc @@ -0,0 +1,17 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/timer/elapsed_timer.h" + +namespace base { + +ElapsedTimer::ElapsedTimer() { + begin_ = TimeTicks::Now(); +} + +TimeDelta ElapsedTimer::Elapsed() const { + return TimeTicks::Now() - begin_; +} + +} // namespace base diff --git a/chromium/base/timer/elapsed_timer.h b/chromium/base/timer/elapsed_timer.h new file mode 100644 index 00000000000..3ea2bfadf38 --- /dev/null +++ b/chromium/base/timer/elapsed_timer.h @@ -0,0 +1,30 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TIMER_ELAPSED_TIMER_H_ +#define BASE_TIMER_ELAPSED_TIMER_H_ + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/time/time.h" + +namespace base { + +// A simple wrapper around TimeTicks::Now(). +class BASE_EXPORT ElapsedTimer { + public: + ElapsedTimer(); + + // Returns the time elapsed since object construction. + TimeDelta Elapsed() const; + + private: + TimeTicks begin_; + + DISALLOW_COPY_AND_ASSIGN(ElapsedTimer); +}; + +} // namespace base + +#endif // BASE_TIMER_ELAPSED_TIMER_H_ diff --git a/chromium/base/timer/timer.cc b/chromium/base/timer/timer.cc index aafc01e1304..1c4b10c6de5 100644 --- a/chromium/base/timer/timer.cc +++ b/chromium/base/timer/timer.cc @@ -108,11 +108,14 @@ void Timer::Reset() { } // Set the new desired_run_time_. - desired_run_time_ = TimeTicks::Now() + delay_; + if (delay_ > TimeDelta::FromMicroseconds(0)) + desired_run_time_ = TimeTicks::Now() + delay_; + else + desired_run_time_ = TimeTicks(); // We can use the existing scheduled task if it arrives before the new // desired_run_time_. - if (desired_run_time_ > scheduled_run_time_) { + if (desired_run_time_ >= scheduled_run_time_) { is_running_ = true; return; } @@ -134,10 +137,16 @@ void Timer::PostNewScheduledTask(TimeDelta delay) { DCHECK(scheduled_task_ == NULL); is_running_ = true; scheduled_task_ = new BaseTimerTaskInternal(this); - ThreadTaskRunnerHandle::Get()->PostDelayedTask(posted_from_, - base::Bind(&BaseTimerTaskInternal::Run, base::Owned(scheduled_task_)), - delay); - scheduled_run_time_ = desired_run_time_ = TimeTicks::Now() + delay; + if (delay > TimeDelta::FromMicroseconds(0)) { + ThreadTaskRunnerHandle::Get()->PostDelayedTask(posted_from_, + base::Bind(&BaseTimerTaskInternal::Run, base::Owned(scheduled_task_)), + delay); + scheduled_run_time_ = desired_run_time_ = TimeTicks::Now() + delay; + } else { + ThreadTaskRunnerHandle::Get()->PostTask(posted_from_, + base::Bind(&BaseTimerTaskInternal::Run, base::Owned(scheduled_task_))); + scheduled_run_time_ = desired_run_time_ = TimeTicks(); + } // Remember the thread ID that posts the first task -- this will be verified // later when the task is abandoned to detect misuse from multiple threads. if (!thread_id_) diff --git a/chromium/base/timer/timer.h b/chromium/base/timer/timer.h index f952d414abf..71a7ae8888a 100644 --- a/chromium/base/timer/timer.h +++ b/chromium/base/timer/timer.h @@ -148,7 +148,8 @@ class BASE_EXPORT Timer { base::Closure user_task_; // The estimated time that the MessageLoop will run the scheduled_task_ that - // will call RunScheduledTask(). + // will call RunScheduledTask(). This time can be a "zero" TimeTicks if the + // task must be run immediately. TimeTicks scheduled_run_time_; // The desired run time of user_task_. The user may update this at any time, @@ -156,7 +157,8 @@ class BASE_EXPORT Timer { // greater than scheduled_run_time_, a continuation task will be posted to // wait for the remaining time. This allows us to reuse the pending task so as // not to flood the MessageLoop with orphaned tasks when the user code - // excessively Stops and Starts the timer. + // excessively Stops and Starts the timer. This time can be a "zero" TimeTicks + // if the task must be run immediately. TimeTicks desired_run_time_; // Thread ID of current MessageLoop for verifying single-threaded usage. diff --git a/chromium/base/timer/timer_unittest.cc b/chromium/base/timer/timer_unittest.cc index 417d258f2ec..728e861b537 100644 --- a/chromium/base/timer/timer_unittest.cc +++ b/chromium/base/timer/timer_unittest.cc @@ -64,23 +64,24 @@ class OneShotSelfDeletingTimerTester { class RepeatingTimerTester { public: - explicit RepeatingTimerTester(bool* did_run) - : did_run_(did_run), counter_(10) { + explicit RepeatingTimerTester(bool* did_run, const TimeDelta& delay) + : did_run_(did_run), counter_(10), delay_(delay) { } void Start() { - timer_.Start(FROM_HERE, TimeDelta::FromMilliseconds(10), this, - &RepeatingTimerTester::Run); + timer_.Start(FROM_HERE, delay_, this, &RepeatingTimerTester::Run); } private: void Run() { if (--counter_ == 0) { *did_run_ = true; + timer_.Stop(); base::MessageLoop::current()->QuitWhenIdle(); } } bool* did_run_; int counter_; + TimeDelta delay_; base::RepeatingTimer<RepeatingTimerTester> timer_; }; @@ -131,11 +132,12 @@ void RunTest_OneShotSelfDeletingTimer( EXPECT_TRUE(did_run); } -void RunTest_RepeatingTimer(base::MessageLoop::Type message_loop_type) { +void RunTest_RepeatingTimer(base::MessageLoop::Type message_loop_type, + const TimeDelta& delay) { base::MessageLoop loop(message_loop_type); bool did_run = false; - RepeatingTimerTester f(&did_run); + RepeatingTimerTester f(&did_run, delay); f.Start(); base::MessageLoop::current()->Run(); @@ -143,11 +145,12 @@ void RunTest_RepeatingTimer(base::MessageLoop::Type message_loop_type) { EXPECT_TRUE(did_run); } -void RunTest_RepeatingTimer_Cancel(base::MessageLoop::Type message_loop_type) { +void RunTest_RepeatingTimer_Cancel(base::MessageLoop::Type message_loop_type, + const TimeDelta& delay) { base::MessageLoop loop(message_loop_type); bool did_run_a = false; - RepeatingTimerTester* a = new RepeatingTimerTester(&did_run_a); + RepeatingTimerTester* a = new RepeatingTimerTester(&did_run_a, delay); // This should run before the timer expires. base::MessageLoop::current()->DeleteSoon(FROM_HERE, a); @@ -156,7 +159,7 @@ void RunTest_RepeatingTimer_Cancel(base::MessageLoop::Type message_loop_type) { a->Start(); bool did_run_b = false; - RepeatingTimerTester b(&did_run_b); + RepeatingTimerTester b(&did_run_b, delay); b.Start(); base::MessageLoop::current()->Run(); @@ -309,13 +312,29 @@ TEST(TimerTest, OneShotSelfDeletingTimer) { TEST(TimerTest, RepeatingTimer) { for (int i = 0; i < kNumTestingMessageLoops; i++) { - RunTest_RepeatingTimer(testing_message_loops[i]); + RunTest_RepeatingTimer(testing_message_loops[i], + TimeDelta::FromMilliseconds(10)); } } TEST(TimerTest, RepeatingTimer_Cancel) { for (int i = 0; i < kNumTestingMessageLoops; i++) { - RunTest_RepeatingTimer_Cancel(testing_message_loops[i]); + RunTest_RepeatingTimer_Cancel(testing_message_loops[i], + TimeDelta::FromMilliseconds(10)); + } +} + +TEST(TimerTest, RepeatingTimerZeroDelay) { + for (int i = 0; i < kNumTestingMessageLoops; i++) { + RunTest_RepeatingTimer(testing_message_loops[i], + TimeDelta::FromMilliseconds(0)); + } +} + +TEST(TimerTest, RepeatingTimerZeroDelay_Cancel) { + for (int i = 0; i < kNumTestingMessageLoops; i++) { + RunTest_RepeatingTimer_Cancel(testing_message_loops[i], + TimeDelta::FromMilliseconds(0)); } } diff --git a/chromium/base/tracked_objects.cc b/chromium/base/tracked_objects.cc index 0847d5f9163..0db7094c498 100644 --- a/chromium/base/tracked_objects.cc +++ b/chromium/base/tracked_objects.cc @@ -7,6 +7,9 @@ #include <limits.h> #include <stdlib.h> +#include "base/atomicops.h" +#include "base/base_switches.h" +#include "base/command_line.h" #include "base/compiler_specific.h" #include "base/debug/leak_annotations.h" #include "base/logging.h" @@ -51,6 +54,32 @@ const ThreadData::Status kInitialStartupState = // problem with its presence). static const bool kAllowAlternateTimeSourceHandling = true; +inline bool IsProfilerTimingEnabled() { + enum { + UNDEFINED_TIMING, + ENABLED_TIMING, + DISABLED_TIMING, + }; + static base::subtle::Atomic32 timing_enabled = UNDEFINED_TIMING; + // Reading |timing_enabled| is done without barrier because multiple + // initialization is not an issue while the barrier can be relatively costly + // given that this method is sometimes called in a tight loop. + base::subtle::Atomic32 current_timing_enabled = + base::subtle::NoBarrier_Load(&timing_enabled); + if (current_timing_enabled == UNDEFINED_TIMING) { + if (!CommandLine::InitializedForCurrentProcess()) + return true; + current_timing_enabled = + (CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + switches::kProfilerTiming) == + switches::kProfilerTimingDisabledValue) + ? DISABLED_TIMING + : ENABLED_TIMING; + base::subtle::NoBarrier_Store(&timing_enabled, current_timing_enabled); + } + return current_timing_enabled == ENABLED_TIMING; +} + } // namespace //------------------------------------------------------------------------------ @@ -754,7 +783,7 @@ void ThreadData::SetAlternateTimeSource(NowFunction* now_function) { TrackedTime ThreadData::Now() { if (kAllowAlternateTimeSourceHandling && now_function_) return TrackedTime::FromMilliseconds((*now_function_)()); - if (kTrackAllTaskObjects && TrackingStatus()) + if (kTrackAllTaskObjects && IsProfilerTimingEnabled() && TrackingStatus()) return TrackedTime::Now(); return TrackedTime(); // Super fast when disabled, or not compiled. } diff --git a/chromium/base/values.cc b/chromium/base/values.cc index 2d3984e0d42..6068556bb3d 100644 --- a/chromium/base/values.cc +++ b/chromium/base/values.cc @@ -462,13 +462,11 @@ void DictionaryValue::SetStringWithoutPathExpansion( SetWithoutPathExpansion(path, CreateStringValue(in_value)); } -bool DictionaryValue::Get( - const std::string& path, const Value** out_value) const { +bool DictionaryValue::Get(const std::string& path, + const Value** out_value) const { DCHECK(IsStringUTF8(path)); -// LOG(WARNING) << "\n1\n"; std::string current_path(path); const DictionaryValue* current_dictionary = this; -// LOG(WARNING) << "\n2\n"; for (size_t delimiter_position = current_path.find('.'); delimiter_position != std::string::npos; delimiter_position = current_path.find('.')) { @@ -480,7 +478,6 @@ bool DictionaryValue::Get( current_dictionary = child_dictionary; current_path.erase(0, delimiter_position + 1); } -// LOG(WARNING) << "\n3\n"; return current_dictionary->GetWithoutPathExpansion(current_path, out_value); } @@ -755,6 +752,26 @@ bool DictionaryValue::RemoveWithoutPathExpansion(const std::string& key, return true; } +bool DictionaryValue::RemovePath(const std::string& path, + scoped_ptr<Value>* out_value) { + bool result = false; + size_t delimiter_position = path.find('.'); + + if (delimiter_position == std::string::npos) + return RemoveWithoutPathExpansion(path, out_value); + + const std::string subdict_path = path.substr(0, delimiter_position); + DictionaryValue* subdict = NULL; + if (!GetDictionary(subdict_path, &subdict)) + return false; + result = subdict->RemovePath(path.substr(delimiter_position + 1), + out_value); + if (result && subdict->empty()) + RemoveWithoutPathExpansion(subdict_path, NULL); + + return result; +} + DictionaryValue* DictionaryValue::DeepCopyWithoutEmptyChildren() const { Value* copy = CopyWithoutEmptyChildren(this); return copy ? static_cast<DictionaryValue*>(copy) : new DictionaryValue; @@ -785,6 +802,8 @@ DictionaryValue::Iterator::Iterator(const DictionaryValue& target) : target_(target), it_(target.dictionary_.begin()) {} +DictionaryValue::Iterator::~Iterator() {} + DictionaryValue* DictionaryValue::DeepCopy() const { DictionaryValue* result = new DictionaryValue; diff --git a/chromium/base/values.h b/chromium/base/values.h index 3bc1f8bbafa..65121808e64 100644 --- a/chromium/base/values.h +++ b/chromium/base/values.h @@ -324,6 +324,11 @@ class BASE_EXPORT DictionaryValue : public Value { virtual bool RemoveWithoutPathExpansion(const std::string& key, scoped_ptr<Value>* out_value); + // Removes a path, clearing out all dictionaries on |path| that remain empty + // after removing the value at |path|. + virtual bool RemovePath(const std::string& path, + scoped_ptr<Value>* out_value); + // Makes a copy of |this| but doesn't include empty dictionaries and lists in // the copy. This never returns NULL, even if |this| itself is empty. DictionaryValue* DeepCopyWithoutEmptyChildren() const; @@ -343,6 +348,7 @@ class BASE_EXPORT DictionaryValue : public Value { class BASE_EXPORT Iterator { public: explicit Iterator(const DictionaryValue& target); + ~Iterator(); bool IsAtEnd() const { return it_ == target_.dictionary_.end(); } void Advance() { ++it_; } diff --git a/chromium/base/values_unittest.cc b/chromium/base/values_unittest.cc index 733c485d2b1..70acdfd6966 100644 --- a/chromium/base/values_unittest.cc +++ b/chromium/base/values_unittest.cc @@ -323,6 +323,31 @@ TEST(ValuesTest, DictionaryWithoutPathExpansion) { EXPECT_EQ(Value::TYPE_NULL, value4->GetType()); } +TEST(ValuesTest, DictionaryRemovePath) { + DictionaryValue dict; + dict.Set("a.long.way.down", Value::CreateIntegerValue(1)); + dict.Set("a.long.key.path", Value::CreateBooleanValue(true)); + + scoped_ptr<Value> removed_item; + EXPECT_TRUE(dict.RemovePath("a.long.way.down", &removed_item)); + ASSERT_TRUE(removed_item); + EXPECT_TRUE(removed_item->IsType(base::Value::TYPE_INTEGER)); + EXPECT_FALSE(dict.HasKey("a.long.way.down")); + EXPECT_FALSE(dict.HasKey("a.long.way")); + EXPECT_TRUE(dict.Get("a.long.key.path", NULL)); + + removed_item.reset(); + EXPECT_FALSE(dict.RemovePath("a.long.way.down", &removed_item)); + EXPECT_FALSE(removed_item); + EXPECT_TRUE(dict.Get("a.long.key.path", NULL)); + + removed_item.reset(); + EXPECT_TRUE(dict.RemovePath("a.long.key.path", &removed_item)); + ASSERT_TRUE(removed_item); + EXPECT_TRUE(removed_item->IsType(base::Value::TYPE_BOOLEAN)); + EXPECT_TRUE(dict.empty()); +} + TEST(ValuesTest, DeepCopy) { DictionaryValue original_dict; Value* original_null = Value::CreateNullValue(); diff --git a/chromium/base/win/event_trace_controller_unittest.cc b/chromium/base/win/event_trace_controller_unittest.cc index 16bf1e134a5..4d23eddc3bb 100644 --- a/chromium/base/win/event_trace_controller_unittest.cc +++ b/chromium/base/win/event_trace_controller_unittest.cc @@ -164,7 +164,7 @@ TEST_F(EtwTraceControllerTest, StartFileSession) { ScopedTempDir temp_dir; ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); FilePath temp; - ASSERT_TRUE(file_util::CreateTemporaryFileInDir(temp_dir.path(), &temp)); + ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir.path(), &temp)); EtwTraceController controller; HRESULT hr = controller.StartFileSession(session_name_.c_str(), diff --git a/chromium/base/win/scoped_co_mem.h b/chromium/base/win/scoped_co_mem.h index 572999a26c1..fc85114828f 100644 --- a/chromium/base/win/scoped_co_mem.h +++ b/chromium/base/win/scoped_co_mem.h @@ -52,6 +52,10 @@ class ScopedCoMem { mem_ptr_ = ptr; } + T* get() const { + return mem_ptr_; + } + private: T* mem_ptr_; diff --git a/chromium/base/win/scoped_handle.h b/chromium/base/win/scoped_handle.h index d236a70beb4..0d038e010bd 100644 --- a/chromium/base/win/scoped_handle.h +++ b/chromium/base/win/scoped_handle.h @@ -13,21 +13,18 @@ #include "base/logging.h" #include "base/move.h" -namespace base { -namespace win { - // TODO(rvargas): remove this with the rest of the verifier. #if defined(COMPILER_MSVC) -// MSDN says to #include <intrin.h>, but that breaks the VS2005 build. -extern "C" { - void* _ReturnAddress(); -} +#include <intrin.h> #define BASE_WIN_GET_CALLER _ReturnAddress() #elif defined(COMPILER_GCC) #define BASE_WIN_GET_CALLER __builtin_extract_return_addr(\\ __builtin_return_address(0)) #endif +namespace base { +namespace win { + // Generic wrapper for raw handles that takes care of closing handles // automatically. The class interface follows the style of // the ScopedStdioHandle class with a few additions: @@ -42,22 +39,6 @@ class GenericScopedHandle { public: typedef typename Traits::Handle Handle; - // Helper object to contain the effect of Receive() to the function that needs - // a pointer, and allow proper tracking of the handle. - class Receiver { - public: - explicit Receiver(GenericScopedHandle* owner) - : handle_(Traits::NullHandle()), - owner_(owner) {} - ~Receiver() { owner_->Set(handle_); } - - operator Handle*() { return &handle_; } - - private: - Handle handle_; - GenericScopedHandle* owner_; - }; - GenericScopedHandle() : handle_(Traits::NullHandle()) {} explicit GenericScopedHandle(Handle handle) : handle_(Traits::NullHandle()) { @@ -105,16 +86,6 @@ class GenericScopedHandle { return handle_; } - // This method is intended to be used with functions that require a pointer to - // a destination handle, like so: - // void CreateRequiredHandle(Handle* out_handle); - // ScopedHandle a; - // CreateRequiredHandle(a.Receive()); - Receiver Receive() { - DCHECK(!Traits::IsHandleValid(handle_)) << "Handle must be NULL"; - return Receiver(this); - } - // Transfers ownership away from this object. Handle Take() { Handle temp = handle_; diff --git a/chromium/base/win/scoped_handle_unittest.cc b/chromium/base/win/scoped_handle_unittest.cc deleted file mode 100644 index ee2a551d63c..00000000000 --- a/chromium/base/win/scoped_handle_unittest.cc +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "base/win/scoped_handle.h" -#include "testing/gtest/include/gtest/gtest.h" - -void CreateHandle(int value, HANDLE* result) { - *result = reinterpret_cast<HANDLE>(value); -} - -TEST(ScopedHandleTest, Receive) { - base::win::ScopedHandle handle; - int value = 51; - - { - // This is not really the expected use case, but it is a very explicit test. - base::win::ScopedHandle::Receiver a = handle.Receive(); - HANDLE* pointer = a; - *pointer = reinterpret_cast<HANDLE>(value); - } - - EXPECT_EQ(handle.Get(), reinterpret_cast<HANDLE>(value)); - HANDLE to_discard = handle.Take(); - - // The standard use case: - value = 183; - CreateHandle(value, handle.Receive()); - EXPECT_EQ(handle.Get(), reinterpret_cast<HANDLE>(value)); - to_discard = handle.Take(); -} diff --git a/chromium/base/win/scoped_process_information.cc b/chromium/base/win/scoped_process_information.cc index cb7a30e2f54..bb2463774a8 100644 --- a/chromium/base/win/scoped_process_information.cc +++ b/chromium/base/win/scoped_process_information.cc @@ -15,7 +15,7 @@ namespace { // Duplicates source into target, returning true upon success. |target| is // guaranteed to be untouched in case of failure. Succeeds with no side-effects // if source is NULL. -bool CheckAndDuplicateHandle(HANDLE source, HANDLE* target) { +bool CheckAndDuplicateHandle(HANDLE source, ScopedHandle* target) { if (!source) return true; @@ -26,7 +26,7 @@ bool CheckAndDuplicateHandle(HANDLE source, HANDLE* target) { DPLOG(ERROR) << "Failed to duplicate a handle."; return false; } - *target = temp; + target->Set(temp); return true; } @@ -36,13 +36,13 @@ ScopedProcessInformation::ScopedProcessInformation() : process_id_(0), thread_id_(0) { } -ScopedProcessInformation::~ScopedProcessInformation() { - Close(); +ScopedProcessInformation::ScopedProcessInformation( + const PROCESS_INFORMATION& process_info) : process_id_(0), thread_id_(0) { + Set(process_info); } -ScopedProcessInformation::Receiver ScopedProcessInformation::Receive() { - DCHECK(!IsValid()) << "process_information_ must be NULL"; - return Receiver(this); +ScopedProcessInformation::~ScopedProcessInformation() { + Close(); } bool ScopedProcessInformation::IsValid() const { @@ -72,10 +72,8 @@ bool ScopedProcessInformation::DuplicateFrom( DCHECK(!IsValid()) << "target ScopedProcessInformation must be NULL"; DCHECK(other.IsValid()) << "source ScopedProcessInformation must be valid"; - if (CheckAndDuplicateHandle(other.process_handle(), - process_handle_.Receive()) && - CheckAndDuplicateHandle(other.thread_handle(), - thread_handle_.Receive())) { + if (CheckAndDuplicateHandle(other.process_handle(), &process_handle_) && + CheckAndDuplicateHandle(other.thread_handle(), &thread_handle_)) { process_id_ = other.process_id(); thread_id_ = other.thread_id(); return true; diff --git a/chromium/base/win/scoped_process_information.h b/chromium/base/win/scoped_process_information.h index 1f404c2dbac..2e240544122 100644 --- a/chromium/base/win/scoped_process_information.h +++ b/chromium/base/win/scoped_process_information.h @@ -18,33 +18,10 @@ namespace win { // structures. Allows clients to take ownership of either handle independently. class BASE_EXPORT ScopedProcessInformation { public: - // Helper object to contain the effect of Receive() to the funtion that needs - // a pointer. - class Receiver { - public: - explicit Receiver(ScopedProcessInformation* owner) - : info_(), - owner_(owner) {} - ~Receiver() { owner_->Set(info_); } - - operator PROCESS_INFORMATION*() { return &info_; } - - private: - PROCESS_INFORMATION info_; - ScopedProcessInformation* owner_; - }; - ScopedProcessInformation(); + explicit ScopedProcessInformation(const PROCESS_INFORMATION& process_info); ~ScopedProcessInformation(); - // Returns an object that may be passed to API calls such as CreateProcess. - // DCHECKs that the object is not currently holding any handles. - // HANDLEs stored in the returned PROCESS_INFORMATION will be owned by this - // instance. - // The intended use case is something like this: - // if (::CreateProcess(..., startup_info, scoped_proces_info.Receive())) - Receiver Receive(); - // Returns true iff this instance is holding a thread and/or process handle. bool IsValid() const; diff --git a/chromium/base/win/scoped_process_information_unittest.cc b/chromium/base/win/scoped_process_information_unittest.cc index b8ffc4427c9..550076ef9c6 100644 --- a/chromium/base/win/scoped_process_information_unittest.cc +++ b/chromium/base/win/scoped_process_information_unittest.cc @@ -19,11 +19,13 @@ const DWORD kThreadId = 1234; const HANDLE kProcessHandle = reinterpret_cast<HANDLE>(7651); const HANDLE kThreadHandle = reinterpret_cast<HANDLE>(1567); -void MockCreateProcess(PROCESS_INFORMATION* process_info) { - process_info->dwProcessId = kProcessId; - process_info->dwThreadId = kThreadId; - process_info->hProcess = kProcessHandle; - process_info->hThread = kThreadHandle; +void MockCreateProcess(base::win::ScopedProcessInformation* process_info) { + PROCESS_INFORMATION process_information = {}; + process_information.dwProcessId = kProcessId; + process_information.dwThreadId = kThreadId; + process_information.hProcess = kProcessHandle; + process_information.hThread = kThreadHandle; + process_info->Set(process_information); } } // namespace @@ -62,7 +64,7 @@ TEST_F(ScopedProcessInformationTest, InitiallyInvalid) { TEST_F(ScopedProcessInformationTest, Receive) { base::win::ScopedProcessInformation process_info; - MockCreateProcess(process_info.Receive()); + MockCreateProcess(&process_info); EXPECT_TRUE(process_info.IsValid()); EXPECT_EQ(kProcessId, process_info.process_id()); @@ -74,7 +76,7 @@ TEST_F(ScopedProcessInformationTest, Receive) { TEST_F(ScopedProcessInformationTest, TakeProcess) { base::win::ScopedProcessInformation process_info; - MockCreateProcess(process_info.Receive()); + MockCreateProcess(&process_info); HANDLE process = process_info.TakeProcessHandle(); EXPECT_EQ(kProcessHandle, process); @@ -86,7 +88,7 @@ TEST_F(ScopedProcessInformationTest, TakeProcess) { TEST_F(ScopedProcessInformationTest, TakeThread) { base::win::ScopedProcessInformation process_info; - MockCreateProcess(process_info.Receive()); + MockCreateProcess(&process_info); HANDLE thread = process_info.TakeThreadHandle(); EXPECT_EQ(kThreadHandle, thread); @@ -98,7 +100,7 @@ TEST_F(ScopedProcessInformationTest, TakeThread) { TEST_F(ScopedProcessInformationTest, TakeBoth) { base::win::ScopedProcessInformation process_info; - MockCreateProcess(process_info.Receive()); + MockCreateProcess(&process_info); HANDLE process = process_info.TakeProcessHandle(); HANDLE thread = process_info.TakeThreadHandle(); @@ -108,7 +110,7 @@ TEST_F(ScopedProcessInformationTest, TakeBoth) { TEST_F(ScopedProcessInformationTest, TakeWholeStruct) { base::win::ScopedProcessInformation process_info; - MockCreateProcess(process_info.Receive()); + MockCreateProcess(&process_info); PROCESS_INFORMATION to_discard = process_info.Take(); EXPECT_EQ(kProcessId, to_discard.dwProcessId); @@ -119,8 +121,11 @@ TEST_F(ScopedProcessInformationTest, TakeWholeStruct) { } TEST_F(ScopedProcessInformationTest, Duplicate) { + PROCESS_INFORMATION temp_process_information; + DoCreateProcess("ReturnSeven", &temp_process_information); base::win::ScopedProcessInformation process_info; - DoCreateProcess("ReturnSeven", process_info.Receive()); + process_info.Set(temp_process_information); + base::win::ScopedProcessInformation duplicate; duplicate.DuplicateFrom(process_info); @@ -146,15 +151,17 @@ TEST_F(ScopedProcessInformationTest, Duplicate) { } TEST_F(ScopedProcessInformationTest, Set) { - PROCESS_INFORMATION base_process_info = {}; + base::win::ScopedProcessInformation base_process_info; MockCreateProcess(&base_process_info); + PROCESS_INFORMATION base_struct = base_process_info.Take(); + base::win::ScopedProcessInformation process_info; - process_info.Set(base_process_info); + process_info.Set(base_struct); EXPECT_EQ(kProcessId, process_info.process_id()); EXPECT_EQ(kThreadId, process_info.thread_id()); EXPECT_EQ(kProcessHandle, process_info.process_handle()); EXPECT_EQ(kThreadHandle, process_info.thread_handle()); - base_process_info = process_info.Take(); + base_struct = process_info.Take(); } diff --git a/chromium/base/win/shortcut.h b/chromium/base/win/shortcut.h index c68edb9c6fa..0f5fb0f686b 100644 --- a/chromium/base/win/shortcut.h +++ b/chromium/base/win/shortcut.h @@ -130,11 +130,13 @@ BASE_EXPORT bool ResolveShortcut(const FilePath& shortcut_path, string16* args); // Pins a shortcut to the Windows 7 taskbar. The shortcut file must already -// exist and be a shortcut that points to an executable. +// exist and be a shortcut that points to an executable. The app id of the +// shortcut is used to group windows and must be set correctly. BASE_EXPORT bool TaskbarPinShortcutLink(const wchar_t* shortcut); // Unpins a shortcut from the Windows 7 taskbar. The shortcut must exist and -// already be pinned to the taskbar. +// already be pinned to the taskbar. The app id of the shortcut is used as the +// identifier for the taskbar item to remove and must be set correctly. BASE_EXPORT bool TaskbarUnpinShortcutLink(const wchar_t* shortcut); } // namespace win diff --git a/chromium/base/win/shortcut_unittest.cc b/chromium/base/win/shortcut_unittest.cc index b3247b6184f..eaf152eb9dc 100644 --- a/chromium/base/win/shortcut_unittest.cc +++ b/chromium/base/win/shortcut_unittest.cc @@ -52,7 +52,7 @@ class ShortcutTest : public testing::Test { arraysize(kFileContents2)); FilePath icon_path_2; - file_util::CreateTemporaryFileInDir(temp_dir_.path(), &icon_path_2); + base::CreateTemporaryFileInDir(temp_dir_.path(), &icon_path_2); link_properties_2_.set_target(target_file_2); link_properties_2_.set_working_dir(temp_dir_2_.path()); @@ -91,7 +91,7 @@ TEST_F(ShortcutTest, CreateAndResolveShortcut) { EXPECT_TRUE(ResolveShortcut(link_file_, &resolved_name, NULL)); char read_contents[arraysize(kFileContents)]; - file_util::ReadFile(resolved_name, read_contents, arraysize(read_contents)); + base::ReadFile(resolved_name, read_contents, arraysize(read_contents)); EXPECT_STREQ(kFileContents, read_contents); } @@ -104,7 +104,7 @@ TEST_F(ShortcutTest, ResolveShortcutWithArgs) { EXPECT_TRUE(ResolveShortcut(link_file_, &resolved_name, &args)); char read_contents[arraysize(kFileContents)]; - file_util::ReadFile(resolved_name, read_contents, arraysize(read_contents)); + base::ReadFile(resolved_name, read_contents, arraysize(read_contents)); EXPECT_STREQ(kFileContents, read_contents); EXPECT_EQ(link_properties_.arguments, args); } @@ -157,7 +157,7 @@ TEST_F(ShortcutTest, UpdateShortcutUpdateOnlyTargetAndResolve) { EXPECT_TRUE(ResolveShortcut(link_file_, &resolved_name, NULL)); char read_contents[arraysize(kFileContents2)]; - file_util::ReadFile(resolved_name, read_contents, arraysize(read_contents)); + base::ReadFile(resolved_name, read_contents, arraysize(read_contents)); EXPECT_STREQ(kFileContents2, read_contents); } diff --git a/chromium/base/win/startup_information_unittest.cc b/chromium/base/win/startup_information_unittest.cc index 1903564d87b..d637ebd68a1 100644 --- a/chromium/base/win/startup_information_unittest.cc +++ b/chromium/base/win/startup_information_unittest.cc @@ -38,7 +38,6 @@ TEST_F(StartupInformationTest, InheritStdOut) { if (base::win::GetVersion() < base::win::VERSION_VISTA) return; - base::win::ScopedProcessInformation process_info; base::win::StartupInformation startup_info; HANDLE section = ::CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, @@ -65,10 +64,13 @@ TEST_F(StartupInformationTest, InheritStdOut) { std::wstring cmd_line = this->MakeCmdLine("FireInheritedEvents", false).GetCommandLineString(); + PROCESS_INFORMATION temp_process_info = {}; ASSERT_TRUE(::CreateProcess(NULL, const_cast<wchar_t*>(cmd_line.c_str()), NULL, NULL, true, EXTENDED_STARTUPINFO_PRESENT, NULL, NULL, startup_info.startup_info(), - process_info.Receive())) << ::GetLastError(); + &temp_process_info)) << ::GetLastError(); + base::win::ScopedProcessInformation process_info(temp_process_info); + // Only the first event should be signalled EXPECT_EQ(WAIT_OBJECT_0, ::WaitForMultipleObjects(2, events, false, 4000)); diff --git a/chromium/base/win/win_util.cc b/chromium/base/win/win_util.cc index 813af316c00..7b85418a1ea 100644 --- a/chromium/base/win/win_util.cc +++ b/chromium/base/win/win_util.cc @@ -5,6 +5,7 @@ #include "base/win/win_util.h" #include <aclapi.h> +#include <lm.h> #include <shellapi.h> #include <shlobj.h> #include <shobjidl.h> // Must be before propkey. @@ -328,7 +329,7 @@ bool DismissVirtualKeyboard() { typedef HWND (*MetroRootWindow) (); -// As for this writing, GetMonitorInfo function seem to return wrong values +// As of this writing, GetMonitorInfo function seem to return wrong values // for rcWork.left and rcWork.top in case of split screen situation inside // metro mode. In order to get required values we query for core window screen // coordinates. @@ -341,6 +342,11 @@ BOOL GetMonitorInfoWrapper(HMONITOR monitor, MONITORINFO* mi) { static MetroRootWindow root_window = NULL; if (!root_window) { HMODULE metro = base::win::GetMetroModule(); + // There are apparently instances when current process is inside metro + // environment but metro driver dll is not loaded. + if (!metro) { + return ret; + } root_window = reinterpret_cast<MetroRootWindow>( ::GetProcAddress(metro, "GetRootWindow")); } @@ -350,6 +356,16 @@ BOOL GetMonitorInfoWrapper(HMONITOR monitor, MONITORINFO* mi) { return ret; } +bool IsEnrolledToDomain() { + LPWSTR domain; + NETSETUP_JOIN_STATUS join_status; + if(::NetGetJoinInformation(NULL, &domain, &join_status) != NERR_Success) + return false; + ::NetApiBufferFree(domain); + + return join_status == ::NetSetupDomainName; +} + } // namespace win } // namespace base diff --git a/chromium/base/win/win_util.h b/chromium/base/win/win_util.h index 903fcf971b8..eebb64ac8ef 100644 --- a/chromium/base/win/win_util.h +++ b/chromium/base/win/win_util.h @@ -130,6 +130,9 @@ BASE_EXPORT bool DismissVirtualKeyboard(); // see bug #247430 for more details. BASE_EXPORT BOOL GetMonitorInfoWrapper(HMONITOR monitor, MONITORINFO* mi); +// Returns true if the machine is enrolled to a domain. +BASE_EXPORT bool IsEnrolledToDomain(); + } // namespace win } // namespace base diff --git a/chromium/base/win/windows_version.cc b/chromium/base/win/windows_version.cc index 817a2bceb43..a99cd1840ac 100644 --- a/chromium/base/win/windows_version.cc +++ b/chromium/base/win/windows_version.cc @@ -10,6 +10,10 @@ #include "base/strings/utf_string_conversions.h" #include "base/win/registry.h" +namespace { +typedef BOOL (WINAPI *GetProductInfoPtr)(DWORD, DWORD, DWORD, DWORD, PDWORD); +} + namespace base { namespace win { @@ -34,7 +38,7 @@ OSInfo::OSInfo() architecture_(OTHER_ARCHITECTURE), wow64_status_(GetWOW64StatusForProcess(GetCurrentProcess())) { OSVERSIONINFOEX version_info = { sizeof version_info }; - GetVersionEx(reinterpret_cast<OSVERSIONINFO*>(&version_info)); + ::GetVersionEx(reinterpret_cast<OSVERSIONINFO*>(&version_info)); version_number_.major = version_info.dwMajorVersion; version_number_.minor = version_info.dwMinorVersion; version_number_.build = version_info.dwBuildNumber; @@ -68,7 +72,7 @@ OSInfo::OSInfo() service_pack_.minor = version_info.wServicePackMinor; SYSTEM_INFO system_info = { 0 }; - GetNativeSystemInfo(&system_info); + ::GetNativeSystemInfo(&system_info); switch (system_info.wProcessorArchitecture) { case PROCESSOR_ARCHITECTURE_INTEL: architecture_ = X86_ARCHITECTURE; break; case PROCESSOR_ARCHITECTURE_AMD64: architecture_ = X64_ARCHITECTURE; break; @@ -76,6 +80,64 @@ OSInfo::OSInfo() } processors_ = system_info.dwNumberOfProcessors; allocation_granularity_ = system_info.dwAllocationGranularity; + + GetProductInfoPtr get_product_info; + DWORD os_type; + + if (version_info.dwMajorVersion == 6) { + // Only present on Vista+. + get_product_info = reinterpret_cast<GetProductInfoPtr>( + ::GetProcAddress(::GetModuleHandle(L"kernel32.dll"), "GetProductInfo")); + + get_product_info(version_info.dwMajorVersion, version_info.dwMinorVersion, + 0, 0, &os_type); + switch (os_type) { + case PRODUCT_CLUSTER_SERVER: + case PRODUCT_DATACENTER_SERVER: + case PRODUCT_DATACENTER_SERVER_CORE: + case PRODUCT_ENTERPRISE_SERVER: + case PRODUCT_ENTERPRISE_SERVER_CORE: + case PRODUCT_ENTERPRISE_SERVER_IA64: + case PRODUCT_SMALLBUSINESS_SERVER: + case PRODUCT_SMALLBUSINESS_SERVER_PREMIUM: + case PRODUCT_STANDARD_SERVER: + case PRODUCT_STANDARD_SERVER_CORE: + case PRODUCT_WEB_SERVER: + version_type_ = SUITE_SERVER; + break; + case PRODUCT_PROFESSIONAL: + case PRODUCT_ULTIMATE: + case PRODUCT_ENTERPRISE: + case PRODUCT_BUSINESS: + version_type_ = SUITE_PROFESSIONAL; + break; + case PRODUCT_HOME_BASIC: + case PRODUCT_HOME_PREMIUM: + case PRODUCT_STARTER: + default: + version_type_ = SUITE_HOME; + break; + } + } else if (version_info.dwMajorVersion == 5 && + version_info.dwMinorVersion == 2) { + if (version_info.wProductType == VER_NT_WORKSTATION && + system_info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) { + version_type_ = SUITE_PROFESSIONAL; + } else if (version_info.wSuiteMask & VER_SUITE_WH_SERVER ) { + version_type_ = SUITE_HOME; + } else { + version_type_ = SUITE_SERVER; + } + } else if (version_info.dwMajorVersion == 5 && + version_info.dwMinorVersion == 1) { + if(version_info.wSuiteMask & VER_SUITE_PERSONAL) + version_type_ = SUITE_HOME; + else + version_type_ = SUITE_PROFESSIONAL; + } else { + // Windows is pre XP so we don't care but pick a safe default. + version_type_ = SUITE_HOME; + } } OSInfo::~OSInfo() { diff --git a/chromium/base/win/windows_version.h b/chromium/base/win/windows_version.h index 718ba35e087..e51840b0db8 100644 --- a/chromium/base/win/windows_version.h +++ b/chromium/base/win/windows_version.h @@ -30,6 +30,16 @@ enum Version { VERSION_WIN_LAST, // Indicates error condition. }; +// A rough bucketing of the available types of versions of Windows. This is used +// to distinguish enterprise enabled versions from home versions and potentially +// server versions. +enum VersionType { + SUITE_HOME, + SUITE_PROFESSIONAL, + SUITE_SERVER, + SUITE_LAST, +}; + // A singleton that can be used to query various pieces of information about the // OS and process state. Note that this doesn't use the base Singleton class, so // it can be used without an AtExitManager. @@ -74,6 +84,7 @@ class BASE_EXPORT OSInfo { Version version() const { return version_; } // The next two functions return arrays of values, [major, minor(, build)]. VersionNumber version_number() const { return version_number_; } + VersionType version_type() const { return version_type_; } ServicePack service_pack() const { return service_pack_; } WindowsArchitecture architecture() const { return architecture_; } int processors() const { return processors_; } @@ -91,6 +102,7 @@ class BASE_EXPORT OSInfo { Version version_; VersionNumber version_number_; + VersionType version_type_; ServicePack service_pack_; WindowsArchitecture architecture_; int processors_; diff --git a/chromium/base/x11/edid_parser_x11.cc b/chromium/base/x11/edid_parser_x11.cc new file mode 100644 index 00000000000..74b14b65c43 --- /dev/null +++ b/chromium/base/x11/edid_parser_x11.cc @@ -0,0 +1,196 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/x11/edid_parser_x11.h" + +#include <X11/extensions/Xrandr.h> +#include <X11/Xatom.h> +#include <X11/Xlib.h> + +#include "base/hash.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/string_util.h" +#include "base/sys_byteorder.h" + +namespace { + +// Returns 64-bit persistent ID for the specified manufacturer's ID and +// product_code_hash, and the index of the output it is connected to. +// |output_index| is used to distinguish the displays of the same type. For +// example, swapping two identical display between two outputs will not be +// treated as swap. The 'serial number' field in EDID isn't used here because +// it is not guaranteed to have unique number and it may have the same fixed +// value (like 0). +int64 GetID(uint16 manufacturer_id, + uint32 product_code_hash, + uint8 output_index) { + return ((static_cast<int64>(manufacturer_id) << 40) | + (static_cast<int64>(product_code_hash) << 8) | output_index); +} + +bool IsRandRAvailable() { + int randr_version_major = 0; + int randr_version_minor = 0; + static bool is_randr_available = XRRQueryVersion( + base::MessagePumpX11::GetDefaultXDisplay(), + &randr_version_major, &randr_version_minor); + return is_randr_available; +} + +} // namespace + +namespace base { + +bool GetEDIDProperty(XID output, unsigned long* nitems, unsigned char** prop) { + if (!IsRandRAvailable()) + return false; + + Display* display = base::MessagePumpX11::GetDefaultXDisplay(); + + static Atom edid_property = XInternAtom( + base::MessagePumpX11::GetDefaultXDisplay(), + RR_PROPERTY_RANDR_EDID, false); + + bool has_edid_property = false; + int num_properties = 0; + Atom* properties = XRRListOutputProperties(display, output, &num_properties); + for (int i = 0; i < num_properties; ++i) { + if (properties[i] == edid_property) { + has_edid_property = true; + break; + } + } + XFree(properties); + if (!has_edid_property) + return false; + + Atom actual_type; + int actual_format; + unsigned long bytes_after; + XRRGetOutputProperty(display, + output, + edid_property, + 0, // offset + 128, // length + false, // _delete + false, // pending + AnyPropertyType, // req_type + &actual_type, + &actual_format, + nitems, + &bytes_after, + prop); + DCHECK_EQ(XA_INTEGER, actual_type); + DCHECK_EQ(8, actual_format); + return true; +} + +bool GetDisplayId(XID output_id, size_t output_index, int64* display_id_out) { + unsigned long nitems = 0; + unsigned char* prop = NULL; + if (!GetEDIDProperty(output_id, &nitems, &prop)) + return false; + + bool result = + GetDisplayIdFromEDID(prop, nitems, output_index, display_id_out); + XFree(prop); + return result; +} + +bool GetDisplayIdFromEDID(const unsigned char* prop, + unsigned long nitems, + size_t output_index, + int64* display_id_out) { + uint16 manufacturer_id = 0; + std::string product_name; + + // ParseOutputDeviceData fails if it doesn't have product_name. + ParseOutputDeviceData(prop, nitems, &manufacturer_id, &product_name); + + // Generates product specific value from product_name instead of product code. + // See crbug.com/240341 + uint32 product_code_hash = product_name.empty() ? + 0 : base::Hash(product_name); + if (manufacturer_id != 0) { + // An ID based on display's index will be assigned later if this call + // fails. + *display_id_out = GetID( + manufacturer_id, product_code_hash, output_index); + return true; + } + return false; +} + +bool ParseOutputDeviceData(const unsigned char* prop, + unsigned long nitems, + uint16* manufacturer_id, + std::string* human_readable_name) { + // See http://en.wikipedia.org/wiki/Extended_display_identification_data + // for the details of EDID data format. We use the following data: + // bytes 8-9: manufacturer EISA ID, in big-endian + // bytes 54-125: four descriptors (18-bytes each) which may contain + // the display name. + const unsigned int kManufacturerOffset = 8; + const unsigned int kManufacturerLength = 2; + const unsigned int kDescriptorOffset = 54; + const unsigned int kNumDescriptors = 4; + const unsigned int kDescriptorLength = 18; + // The specifier types. + const unsigned char kMonitorNameDescriptor = 0xfc; + + if (manufacturer_id) { + if (nitems < kManufacturerOffset + kManufacturerLength) { + LOG(ERROR) << "too short EDID data: manifacturer id"; + return false; + } + + *manufacturer_id = + *reinterpret_cast<const uint16*>(prop + kManufacturerOffset); +#if defined(ARCH_CPU_LITTLE_ENDIAN) + *manufacturer_id = base::ByteSwap(*manufacturer_id); +#endif + } + + if (!human_readable_name) + return true; + + human_readable_name->clear(); + for (unsigned int i = 0; i < kNumDescriptors; ++i) { + if (nitems < kDescriptorOffset + (i + 1) * kDescriptorLength) + break; + + const unsigned char* desc_buf = + prop + kDescriptorOffset + i * kDescriptorLength; + // If the descriptor contains the display name, it has the following + // structure: + // bytes 0-2, 4: \0 + // byte 3: descriptor type, defined above. + // bytes 5-17: text data, ending with \r, padding with spaces + // we should check bytes 0-2 and 4, since it may have other values in + // case that the descriptor contains other type of data. + if (desc_buf[0] == 0 && desc_buf[1] == 0 && desc_buf[2] == 0 && + desc_buf[4] == 0) { + if (desc_buf[3] == kMonitorNameDescriptor) { + std::string found_name( + reinterpret_cast<const char*>(desc_buf + 5), kDescriptorLength - 5); + TrimWhitespaceASCII(found_name, TRIM_TRAILING, human_readable_name); + break; + } + } + } + + // Verify if the |human_readable_name| consists of printable characters only. + for (size_t i = 0; i < human_readable_name->size(); ++i) { + char c = (*human_readable_name)[i]; + if (!isascii(c) || !isprint(c)) { + human_readable_name->clear(); + LOG(ERROR) << "invalid EDID: human unreadable char in name"; + return false; + } + } + + return true; +} + +} // namespace base diff --git a/chromium/base/x11/edid_parser_x11.h b/chromium/base/x11/edid_parser_x11.h new file mode 100644 index 00000000000..0fba0b54d7b --- /dev/null +++ b/chromium/base/x11/edid_parser_x11.h @@ -0,0 +1,54 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_X11_EDID_PARSER_X11_H_ +#define BASE_X11_EDID_PARSER_X11_H_ + +#include <string> + +#include "base/base_export.h" +#include "base/basictypes.h" + +typedef unsigned long XID; + +// EDID (Extended Display Identification Data) is a format for monitor +// metadata. This provides a parser for the data and an interface to get it +// from XRandR. + +namespace base { + +// Get the EDID data from the |output| and stores to |prop|. |nitem| will store +// the number of characters |prop| will have. It doesn't take the ownership of +// |prop|, so caller must release it by XFree(). +// Returns true if EDID property is successfully obtained. Otherwise returns +// false and does not touch |prop| and |nitems|. +BASE_EXPORT bool GetEDIDProperty(XID output, + unsigned long* nitems, + unsigned char** prop); + +// Gets the EDID data from |output| and generates the display id through +// |GetDisplayIdFromEDID|. +BASE_EXPORT bool GetDisplayId(XID output, size_t index, + int64* display_id_out); + +// Generates the display id for the pair of |prop| with |nitems| length and +// |index|, and store in |display_id_out|. Returns true if the display id is +// successfully generated, or false otherwise. +BASE_EXPORT bool GetDisplayIdFromEDID(const unsigned char* prop, + unsigned long nitems, + size_t index, + int64* display_id_out); + +// Parses |prop| as EDID data and stores extracted data into |manufacturer_id| +// and |human_readable_name| and returns true. NULL can be passed for unwanted +// output parameters. Some devices (especially internal displays) may not have +// the field for |human_readable_name|, and it will return true in that case. +BASE_EXPORT bool ParseOutputDeviceData(const unsigned char* prop, + unsigned long nitems, + uint16* manufacturer_id, + std::string* human_readable_name); + +} // namespace base + +#endif // BASE_X11_EDID_PARSER_X11_H_ diff --git a/chromium/base/x11/edid_parser_x11_unittest.cc b/chromium/base/x11/edid_parser_x11_unittest.cc new file mode 100644 index 00000000000..97e3ce10340 --- /dev/null +++ b/chromium/base/x11/edid_parser_x11_unittest.cc @@ -0,0 +1,167 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/x11/edid_parser_x11.h" + +#include "base/memory/scoped_ptr.h" +#include "testing/gtest/include/gtest/gtest.h" + +#include <X11/extensions/Xrandr.h> + +namespace base { + +namespace { + +// Returns the number of characters in the string literal but doesn't count its +// terminator NULL byte. +#define charsize(str) (arraysize(str) - 1) + +// Sample EDID data extracted from real devices. +const unsigned char kNormalDisplay[] = + "\x00\xff\xff\xff\xff\xff\xff\x00\x22\xf0\x6c\x28\x01\x01\x01\x01" + "\x02\x16\x01\x04\xb5\x40\x28\x78\xe2\x8d\x85\xad\x4f\x35\xb1\x25" + "\x0e\x50\x54\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01" + "\x01\x01\x01\x01\x01\x01\xe2\x68\x00\xa0\xa0\x40\x2e\x60\x30\x20" + "\x36\x00\x81\x90\x21\x00\x00\x1a\xbc\x1b\x00\xa0\x50\x20\x17\x30" + "\x30\x20\x36\x00\x81\x90\x21\x00\x00\x1a\x00\x00\x00\xfc\x00\x48" + "\x50\x20\x5a\x52\x33\x30\x77\x0a\x20\x20\x20\x20\x00\x00\x00\xff" + "\x00\x43\x4e\x34\x32\x30\x32\x31\x33\x37\x51\x0a\x20\x20\x00\x71"; + +const unsigned char kInternalDisplay[] = + "\x00\xff\xff\xff\xff\xff\xff\x00\x4c\xa3\x42\x31\x00\x00\x00\x00" + "\x00\x15\x01\x03\x80\x1a\x10\x78\x0a\xd3\xe5\x95\x5c\x60\x90\x27" + "\x19\x50\x54\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01" + "\x01\x01\x01\x01\x01\x01\x9e\x1b\x00\xa0\x50\x20\x12\x30\x10\x30" + "\x13\x00\x05\xa3\x10\x00\x00\x19\x00\x00\x00\x0f\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x23\x87\x02\x64\x00\x00\x00\x00\xfe\x00\x53" + "\x41\x4d\x53\x55\x4e\x47\x0a\x20\x20\x20\x20\x20\x00\x00\x00\xfe" + "\x00\x31\x32\x31\x41\x54\x31\x31\x2d\x38\x30\x31\x0a\x20\x00\x45"; + +const unsigned char kOverscanDisplay[] = + "\x00\xff\xff\xff\xff\xff\xff\x00\x4c\x2d\xfe\x08\x00\x00\x00\x00" + "\x29\x15\x01\x03\x80\x10\x09\x78\x0a\xee\x91\xa3\x54\x4c\x99\x26" + "\x0f\x50\x54\xbd\xef\x80\x71\x4f\x81\xc0\x81\x00\x81\x80\x95\x00" + "\xa9\xc0\xb3\x00\x01\x01\x02\x3a\x80\x18\x71\x38\x2d\x40\x58\x2c" + "\x45\x00\xa0\x5a\x00\x00\x00\x1e\x66\x21\x56\xaa\x51\x00\x1e\x30" + "\x46\x8f\x33\x00\xa0\x5a\x00\x00\x00\x1e\x00\x00\x00\xfd\x00\x18" + "\x4b\x0f\x51\x17\x00\x0a\x20\x20\x20\x20\x20\x20\x00\x00\x00\xfc" + "\x00\x53\x41\x4d\x53\x55\x4e\x47\x0a\x20\x20\x20\x20\x20\x01\x1d" + "\x02\x03\x1f\xf1\x47\x90\x04\x05\x03\x20\x22\x07\x23\x09\x07\x07" + "\x83\x01\x00\x00\xe2\x00\x0f\x67\x03\x0c\x00\x20\x00\xb8\x2d\x01" + "\x1d\x80\x18\x71\x1c\x16\x20\x58\x2c\x25\x00\xa0\x5a\x00\x00\x00" + "\x9e\x01\x1d\x00\x72\x51\xd0\x1e\x20\x6e\x28\x55\x00\xa0\x5a\x00" + "\x00\x00\x1e\x8c\x0a\xd0\x8a\x20\xe0\x2d\x10\x10\x3e\x96\x00\xa0" + "\x5a\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc6"; + +const unsigned char kLP2565A[] = + "\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00\x22\xF0\x76\x26\x01\x01\x01\x01" + "\x02\x12\x01\x03\x80\x34\x21\x78\xEE\xEF\x95\xA3\x54\x4C\x9B\x26" + "\x0F\x50\x54\xA5\x6B\x80\x81\x40\x81\x80\x81\x99\x71\x00\xA9\x00" + "\xA9\x40\xB3\x00\xD1\x00\x28\x3C\x80\xA0\x70\xB0\x23\x40\x30\x20" + "\x36\x00\x07\x44\x21\x00\x00\x1A\x00\x00\x00\xFD\x00\x30\x55\x1E" + "\x5E\x11\x00\x0A\x20\x20\x20\x20\x20\x20\x00\x00\x00\xFC\x00\x48" + "\x50\x20\x4C\x50\x32\x34\x36\x35\x0A\x20\x20\x20\x00\x00\x00\xFF" + "\x00\x43\x4E\x4B\x38\x30\x32\x30\x34\x48\x4D\x0A\x20\x20\x00\xA4"; + +const unsigned char kLP2565B[] = + "\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00\x22\xF0\x75\x26\x01\x01\x01\x01" + "\x02\x12\x01\x03\x6E\x34\x21\x78\xEE\xEF\x95\xA3\x54\x4C\x9B\x26" + "\x0F\x50\x54\xA5\x6B\x80\x81\x40\x71\x00\xA9\x00\xA9\x40\xA9\x4F" + "\xB3\x00\xD1\xC0\xD1\x00\x28\x3C\x80\xA0\x70\xB0\x23\x40\x30\x20" + "\x36\x00\x07\x44\x21\x00\x00\x1A\x00\x00\x00\xFD\x00\x30\x55\x1E" + "\x5E\x15\x00\x0A\x20\x20\x20\x20\x20\x20\x00\x00\x00\xFC\x00\x48" + "\x50\x20\x4C\x50\x32\x34\x36\x35\x0A\x20\x20\x20\x00\x00\x00\xFF" + "\x00\x43\x4E\x4B\x38\x30\x32\x30\x34\x48\x4D\x0A\x20\x20\x00\x45"; + +} // namespace + +TEST(EdidParserX11Test, ParseEDID) { + uint16 manufacturer_id = 0; + std::string human_readable_name; + EXPECT_TRUE(ParseOutputDeviceData( + kNormalDisplay, charsize(kNormalDisplay), + &manufacturer_id, &human_readable_name)); + EXPECT_EQ(0x22f0u, manufacturer_id); + EXPECT_EQ("HP ZR30w", human_readable_name); + + manufacturer_id = 0; + human_readable_name.clear(); + EXPECT_TRUE(ParseOutputDeviceData( + kInternalDisplay, charsize(kInternalDisplay), + &manufacturer_id, NULL)); + EXPECT_EQ(0x4ca3u, manufacturer_id); + EXPECT_EQ("", human_readable_name); + + // Internal display doesn't have name. + EXPECT_TRUE(ParseOutputDeviceData( + kInternalDisplay, charsize(kInternalDisplay), + NULL, &human_readable_name)); + EXPECT_TRUE(human_readable_name.empty()); + + manufacturer_id = 0; + human_readable_name.clear(); + EXPECT_TRUE(ParseOutputDeviceData( + kOverscanDisplay, charsize(kOverscanDisplay), + &manufacturer_id, &human_readable_name)); + EXPECT_EQ(0x4c2du, manufacturer_id); + EXPECT_EQ("SAMSUNG", human_readable_name); +} + +TEST(EdidParserX11Test, ParseBrokenEDID) { + uint16 manufacturer_id = 0; + std::string human_readable_name; + + // length == 0 + EXPECT_FALSE(ParseOutputDeviceData( + kNormalDisplay, 0, + &manufacturer_id, &human_readable_name)); + + // name is broken. Copying kNormalDisplay and substitute its name data by + // some control code. + std::string display_data( + reinterpret_cast<const char*>(kNormalDisplay), charsize(kNormalDisplay)); + + // display's name data is embedded in byte 95-107 in this specific example. + // Fix here too when the contents of kNormalDisplay is altered. + display_data[97] = '\x1b'; + EXPECT_FALSE(ParseOutputDeviceData( + reinterpret_cast<const unsigned char*>(display_data.data()), + display_data.size(), + &manufacturer_id, &human_readable_name)); + + // If |human_readable_name| isn't specified, it skips parsing the name. + manufacturer_id = 0; + EXPECT_TRUE(ParseOutputDeviceData( + reinterpret_cast<const unsigned char*>(display_data.data()), + display_data.size(), + &manufacturer_id, NULL)); + EXPECT_EQ(0x22f0u, manufacturer_id); +} + +TEST(EdidParserX11Test, GetDisplayId) { + // EDID of kLP2565A and B are slightly different but actually the same device. + int64 id1 = -1; + int64 id2 = -1; + EXPECT_TRUE(GetDisplayIdFromEDID(kLP2565A, charsize(kLP2565A), 0, &id1)); + EXPECT_TRUE(GetDisplayIdFromEDID(kLP2565B, charsize(kLP2565B), 0, &id2)); + EXPECT_EQ(id1, id2); + EXPECT_NE(-1, id1); +} + +TEST(EdidParserX11Test, GetDisplayIdFromInternal) { + int64 id = -1; + EXPECT_TRUE(GetDisplayIdFromEDID( + kInternalDisplay, charsize(kInternalDisplay), 0, &id)); + EXPECT_NE(-1, id); +} + +TEST(EdidParserX11Test, GetDisplayIdFailure) { + int64 id = -1; + EXPECT_FALSE(GetDisplayIdFromEDID(NULL, 0, 0, &id)); + EXPECT_EQ(-1, id); +} + +} // namespace base diff --git a/chromium/base/x11/x11_error_tracker.cc b/chromium/base/x11/x11_error_tracker.cc new file mode 100644 index 00000000000..446408c5c3d --- /dev/null +++ b/chromium/base/x11/x11_error_tracker.cc @@ -0,0 +1,37 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/x11/x11_error_tracker.h" + +#include "base/message_loop/message_pump_x11.h" + +namespace { + +unsigned char g_x11_error_code = 0; + +int X11ErrorHandler(Display* display, XErrorEvent* error) { + g_x11_error_code = error->error_code; + return 0; +} + +} + +namespace base { + +X11ErrorTracker::X11ErrorTracker() { + old_handler_ = XSetErrorHandler(X11ErrorHandler); +} + +X11ErrorTracker::~X11ErrorTracker() { + XSetErrorHandler(old_handler_); +} + +bool X11ErrorTracker::FoundNewError() { + XSync(MessagePumpForUI::GetDefaultXDisplay(), False); + unsigned char error = g_x11_error_code; + g_x11_error_code = 0; + return error != 0; +} + +} // namespace ui diff --git a/chromium/base/x11/x11_error_tracker.h b/chromium/base/x11/x11_error_tracker.h new file mode 100644 index 00000000000..5542f52a056 --- /dev/null +++ b/chromium/base/x11/x11_error_tracker.h @@ -0,0 +1,38 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_X11_X11_ERROR_TRACKER_H_ +#define BASE_X11_X11_ERROR_TRACKER_H_ + +#include <X11/Xlib.h> + +#include "base/base_export.h" +#include "base/basictypes.h" + +namespace base { + +// X11ErrorTracker catches X11 errors in a non-fatal way. It does so by +// temporarily changing the X11 error handler. The old error handler is +// restored when the tracker is destroyed. +class BASE_EXPORT X11ErrorTracker { + public: + X11ErrorTracker(); + ~X11ErrorTracker(); + + // Returns whether an X11 error happened since this function was last called + // (or since the creation of the tracker). This is potentially expensive, + // since this causes a sync with the X server. + bool FoundNewError(); + + private: +#if !defined(TOOLKIT_GTK) + XErrorHandler old_handler_; +#endif + + DISALLOW_COPY_AND_ASSIGN(X11ErrorTracker); +}; + +} // namespace base + +#endif // BASE_X11_X11_ERROR_TRACKER_H_ diff --git a/chromium/base/x11/x11_error_tracker_gtk.cc b/chromium/base/x11/x11_error_tracker_gtk.cc new file mode 100644 index 00000000000..7a78a7c2750 --- /dev/null +++ b/chromium/base/x11/x11_error_tracker_gtk.cc @@ -0,0 +1,29 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/x11/x11_error_tracker.h" + +#include <gdk/gdkx.h> + +#include "base/logging.h" + +namespace base { + +X11ErrorTracker::X11ErrorTracker() { + gdk_error_trap_push(); +} + +X11ErrorTracker::~X11ErrorTracker() { + gdk_error_trap_pop(); +} + +bool X11ErrorTracker::FoundNewError() { + gdk_flush(); + bool found_error = gdk_error_trap_pop() != 0; + + gdk_error_trap_push(); + return found_error; +} + +} // namespace base |